*/ private const FIRST_NAMES = [ 'Ama', 'Kofi', 'Abena', 'Kwame', 'Akosua', 'Yaw', 'Efua', 'Kojo', 'Adwoa', 'Kwesi', 'Afia', 'Kwadwo', 'Esi', 'Fiifi', 'Mansa', 'Nana', ]; /** @var list */ private const LAST_NAMES = [ 'Mensah', 'Asante', 'Osei', 'Boateng', 'Owusu', 'Adjei', 'Appiah', 'Darko', 'Agyeman', 'Sarpong', 'Nyarko', 'Ankrah', 'Tetteh', 'Quaye', 'Addo', 'Frimpong', ]; /** @var list */ private const BRANCH_NAMES = [ 'Ridge Clinic', 'Osu Medical Centre', 'East Legon Care', 'Tema Harbour Clinic', 'Kaneshie Health Hub', 'Spintex Wellness', 'Madina Family Clinic', 'Airport Residential Care', ]; /** @var list */ private const CORE_DEPARTMENTS = [ ['name' => 'Outpatient', 'type' => 'outpatient'], ['name' => 'General Practice', 'type' => 'general'], ['name' => 'Nursing', 'type' => 'nursing'], ['name' => 'Specialist Clinic', 'type' => 'specialist'], ]; /** @var list */ private const PAID_DEPARTMENTS = [ ['name' => 'Laboratory', 'type' => 'laboratory'], ['name' => 'Pharmacy', 'type' => 'pharmacy'], ]; /** @var list */ private const SPECIALTIES = [ 'General Practice', 'Internal Medicine', 'Paediatrics', 'Obstetrics', 'Emergency', 'Nursing', 'Laboratory', 'Pharmacy', ]; /** * @return array{ * organization: Organization, * branches: int, * patients: int, * appointments: int, * plan: string * } */ public function seed(User $user, string $plan, bool $reset = false): array { $plan = $this->normalizePlan($plan); $ownerRef = $user->ownerRef(); $volumes = $this->volumes($plan); if ($reset) { DB::transaction(fn () => $this->resetTenant($ownerRef)); } return DB::transaction(function () use ($user, $ownerRef, $plan, $volumes) { $organization = $this->upsertOrganization($user, $ownerRef, $plan, $volumes['branches']); $this->upsertOwnerMember($organization, $ownerRef); $branches = $this->seedBranches($organization, $ownerRef, $volumes); $this->seedStaffMembers($organization, $ownerRef, $plan, $branches); $departments = $this->seedDepartments($branches, $ownerRef, $volumes); $practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes); $patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes); $appointmentCount = $this->seedAppointmentsAndClinical( $organization, $branches, $departments, $practitioners, $patients, $ownerRef, $volumes, ); if ($volumes['paid_modules']) { $this->seedPaidModules( $organization, $branches, $practitioners, $patients, $ownerRef, $volumes, ); } if ($volumes['assessments'] > 0) { $this->seedAssessments( $organization, $branches, $practitioners, $patients, $ownerRef, $volumes['assessments'], ); } return [ 'organization' => $organization->fresh(), 'branches' => count($branches), 'patients' => count($patients), 'appointments' => $appointmentCount, 'plan' => $plan, ]; }); } public function resetTenant(string $ownerRef): void { $orgIds = DB::table('care_organizations')->where('owner_ref', $ownerRef)->pluck('id'); DB::table('care_assessment_answers')->where('owner_ref', $ownerRef)->delete(); DB::table('care_assessment_scores')->where('owner_ref', $ownerRef)->delete(); DB::table('care_assessments')->where('owner_ref', $ownerRef)->update(['patient_pathway_id' => null]); DB::table('care_patient_pathways')->where('owner_ref', $ownerRef)->delete(); DB::table('care_assessments')->where('owner_ref', $ownerRef)->delete(); if ($orgIds->isNotEmpty()) { $templateIds = DB::table('care_assessment_templates') ->whereIn('organization_id', $orgIds) ->pluck('id'); if ($templateIds->isNotEmpty()) { DB::table('care_assessment_questions')->whereIn('template_id', $templateIds)->delete(); DB::table('care_assessment_templates')->whereIn('id', $templateIds)->delete(); } } // Orphaned demo templates left behind by nullOnDelete on prior resets. $orphanIds = DB::table('care_assessment_templates') ->where('code', self::DEMO_TEMPLATE_CODE) ->whereNull('organization_id') ->pluck('id'); if ($orphanIds->isNotEmpty()) { DB::table('care_assessment_questions')->whereIn('template_id', $orphanIds)->delete(); DB::table('care_assessment_templates')->whereIn('id', $orphanIds)->delete(); } DB::table('care_dispensing_records')->where('owner_ref', $ownerRef)->delete(); DB::table('care_payments')->where('owner_ref', $ownerRef)->delete(); DB::table('care_bill_line_items')->where('owner_ref', $ownerRef)->delete(); DB::table('care_bills')->where('owner_ref', $ownerRef)->delete(); DB::table('care_investigation_result_values')->where('owner_ref', $ownerRef)->delete(); DB::table('care_investigation_attachments')->where('owner_ref', $ownerRef)->delete(); DB::table('care_investigation_results')->where('owner_ref', $ownerRef)->delete(); DB::table('care_investigation_requests')->where('owner_ref', $ownerRef)->delete(); DB::table('care_prescription_items')->where('owner_ref', $ownerRef)->delete(); DB::table('care_prescriptions')->where('owner_ref', $ownerRef)->delete(); DB::table('care_drug_batches')->where('owner_ref', $ownerRef)->delete(); DB::table('care_drugs')->where('owner_ref', $ownerRef)->delete(); DB::table('care_investigation_types')->where('owner_ref', $ownerRef)->delete(); DB::table('care_consultation_documents')->where('owner_ref', $ownerRef)->delete(); DB::table('care_diagnoses')->where('owner_ref', $ownerRef)->delete(); DB::table('care_vital_signs')->where('owner_ref', $ownerRef)->delete(); DB::table('care_consultations')->where('owner_ref', $ownerRef)->delete(); DB::table('care_appointments')->where('owner_ref', $ownerRef)->delete(); DB::table('care_visits')->where('owner_ref', $ownerRef)->delete(); DB::table('care_patient_allergies')->where('owner_ref', $ownerRef)->delete(); DB::table('care_patient_conditions')->where('owner_ref', $ownerRef)->delete(); DB::table('care_patient_family_history')->where('owner_ref', $ownerRef)->delete(); DB::table('care_emergency_contacts')->where('owner_ref', $ownerRef)->delete(); DB::table('care_insurance_policies')->where('owner_ref', $ownerRef)->delete(); DB::table('care_patient_attachments')->where('owner_ref', $ownerRef)->delete(); DB::table('care_patients')->where('owner_ref', $ownerRef)->delete(); DB::table('care_practitioner_schedules')->where('owner_ref', $ownerRef)->delete(); DB::table('care_practitioners')->where('owner_ref', $ownerRef)->delete(); DB::table('care_members')->where('owner_ref', $ownerRef)->delete(); DB::table('care_departments')->where('owner_ref', $ownerRef)->delete(); DB::table('care_branches')->where('owner_ref', $ownerRef)->delete(); DB::table('care_messaging_credentials')->where('owner_ref', $ownerRef)->delete(); DB::table('care_payment_gateway_settings')->where('owner_ref', $ownerRef)->delete(); DB::table('care_audit_logs')->where('owner_ref', $ownerRef)->delete(); DB::table('care_organizations')->where('owner_ref', $ownerRef)->delete(); } /** * @return array{ * branches: int, * departments_per_branch: int, * practitioners: int, * patients: int, * appointments: int, * paid_modules: bool, * assessments: int, * lab_requests: int, * prescriptions: int, * bills: int * } */ public function volumes(string $plan): array { $testing = app()->runningUnitTests(); return match ($this->normalizePlan($plan)) { 'free' => [ 'branches' => 1, 'departments_per_branch' => 2, 'practitioners' => 3, 'patients' => $testing ? 12 : 30, 'appointments' => $testing ? 20 : 50, 'paid_modules' => false, 'assessments' => 0, 'lab_requests' => 0, 'prescriptions' => 0, 'bills' => 0, ], 'pro' => [ 'branches' => 3, 'departments_per_branch' => 3, 'practitioners' => 8, 'patients' => $testing ? 24 : 200, 'appointments' => $testing ? 40 : 300, 'paid_modules' => true, 'assessments' => 0, 'lab_requests' => $testing ? 8 : 40, 'prescriptions' => $testing ? 8 : 40, 'bills' => $testing ? 10 : 60, ], 'enterprise' => [ 'branches' => 6, 'departments_per_branch' => 4, 'practitioners' => 16, 'patients' => $testing ? 36 : 500, 'appointments' => $testing ? 60 : 800, 'paid_modules' => true, 'assessments' => $testing ? 12 : 80, 'lab_requests' => $testing ? 12 : 80, 'prescriptions' => $testing ? 12 : 80, 'bills' => $testing ? 15 : 120, ], default => throw new \InvalidArgumentException("Unsupported plan [{$plan}]"), }; } private function normalizePlan(string $plan): string { $plan = strtolower(trim($plan)); return in_array($plan, ['free', 'pro', 'enterprise'], true) ? $plan : 'free'; } private function upsertOrganization(User $user, string $ownerRef, string $plan, int $billedBranches): Organization { $slug = 'demo-'.Str::slug(Str::limit($user->email ?: $ownerRef, 40, '')); if ($slug === 'demo-' || $slug === 'demo') { $slug = 'demo-'.Str::lower(Str::substr($ownerRef, 0, 8)); } $name = DemoWorld::business($plan)['name']; $organization = Organization::withTrashed() ->where('owner_ref', $ownerRef) ->first(); $settings = array_merge($organization?->settings ?? [], [ 'onboarded' => true, 'facility_type' => $plan === 'enterprise' ? 'hospital' : 'clinic', 'plan' => $plan, 'plan_expires_at' => now()->addYear()->toIso8601String(), 'billed_branches' => max(1, $billedBranches), 'demo' => true, ]); if ($organization) { if ($organization->trashed()) { $organization->restore(); } $organization->update([ 'name' => $name, 'slug' => $slug, 'timezone' => 'Africa/Accra', 'settings' => $settings, ]); return $organization->fresh(); } return Organization::create([ 'owner_ref' => $ownerRef, 'name' => $name, 'slug' => $slug, 'timezone' => 'Africa/Accra', 'settings' => $settings, ]); } private function upsertOwnerMember(Organization $organization, string $ownerRef): Member { return Member::query()->updateOrCreate( [ 'organization_id' => $organization->id, 'user_ref' => $ownerRef, ], [ 'owner_ref' => $ownerRef, 'role' => 'hospital_admin', 'branch_id' => null, ], ); } /** * @param array $volumes * @return list */ private function seedBranches(Organization $organization, string $ownerRef, array $volumes): array { $catalog = DemoWorld::branches($this->normalizePlan( (string) data_get($organization->settings, 'plan', 'free') ), $volumes['branches']); $branches = []; foreach ($catalog as $i => $spec) { $code = $spec['code'] !== '' ? $spec['code'] : sprintf('DEMO-BR-%02d', $i + 1); $branches[] = Branch::withTrashed()->updateOrCreate( [ 'organization_id' => $organization->id, 'code' => $code, ], [ 'owner_ref' => $ownerRef, 'name' => $spec['name'], 'address' => $spec['address'], 'phone' => $spec['phone'], 'is_active' => true, 'deleted_at' => null, ], ); } return $branches; } /** * Local Member rows for DemoWorld staff (user_ref = email until SSO remaps to public_id). * * @param list $branches */ private function seedStaffMembers(Organization $organization, string $ownerRef, string $plan, array $branches): void { $hq = $branches[0] ?? null; foreach (DemoWorld::staff($plan) as $staff) { $role = $staff['roles']['care'] ?? null; if (! is_string($role) || $role === '') { continue; } Member::query()->updateOrCreate( [ 'organization_id' => $organization->id, 'user_ref' => strtolower($staff['email']), ], [ 'owner_ref' => $ownerRef, 'role' => $role, 'branch_id' => in_array($role, ['hospital_admin'], true) ? null : $hq?->id, ], ); } } /** * @param list $branches * @param array $volumes * @return array> keyed by branch id */ private function seedDepartments(array $branches, string $ownerRef, array $volumes): array { $catalog = self::CORE_DEPARTMENTS; if ($volumes['paid_modules']) { $catalog = array_merge($catalog, self::PAID_DEPARTMENTS); } $byBranch = []; foreach ($branches as $branch) { $byBranch[$branch->id] = []; $count = min($volumes['departments_per_branch'], count($catalog)); for ($i = 0; $i < $count; $i++) { $spec = $catalog[$i]; $byBranch[$branch->id][] = Department::withTrashed()->updateOrCreate( [ 'branch_id' => $branch->id, 'name' => $spec['name'], ], [ 'owner_ref' => $ownerRef, 'type' => $spec['type'], 'is_active' => true, 'deleted_at' => null, ], ); } } return $byBranch; } /** * @param list $branches * @param array> $departments * @param array $volumes * @return list */ private function seedPractitioners( Organization $organization, array $branches, array $departments, string $ownerRef, array $volumes, ): array { $practitioners = []; for ($i = 0; $i < $volumes['practitioners']; $i++) { $branch = $branches[$i % count($branches)]; $branchDepts = $departments[$branch->id] ?? []; $department = $branchDepts[$i % max(1, count($branchDepts))] ?? null; $key = sprintf('demo-prac-%s-%02d', $ownerRef, $i + 1); $practitioners[] = Practitioner::withTrashed()->updateOrCreate( [ 'organization_id' => $organization->id, 'user_ref' => $key, ], [ 'owner_ref' => $ownerRef, 'branch_id' => $branch->id, 'department_id' => $department?->id, 'member_id' => null, 'name' => 'Dr. '.self::FIRST_NAMES[$i % count(self::FIRST_NAMES)].' '.self::LAST_NAMES[$i % count(self::LAST_NAMES)], 'specialty' => self::SPECIALTIES[$i % count(self::SPECIALTIES)], 'is_active' => true, 'deleted_at' => null, ], ); } return $practitioners; } /** * @param list $branches * @param array $volumes * @return list */ private function seedPatients( Organization $organization, array $branches, string $ownerRef, array $volumes, ): array { $worldPeople = DemoWorld::people(); $patients = []; for ($i = 0; $i < $volumes['patients']; $i++) { $branch = $branches[$i % count($branches)]; $number = sprintf('DEMO-%s-%05d', Str::upper(Str::substr(md5($ownerRef), 0, 4)), $i + 1); $person = $worldPeople[$i] ?? null; if ($person === null) { $base = $worldPeople[$i % count($worldPeople)]; $seq = $i + 1; $person = [ 'first_name' => $base['first_name'], 'last_name' => $base['last_name'], 'gender' => $base['gender'], 'dob' => $base['dob'], 'phone' => '+23324'.str_pad((string) (1000000 + $seq), 7, '0', STR_PAD_LEFT), 'email' => sprintf('patient.%d@demo.ladill.com', $seq), 'national_id' => sprintf('GHA-729%06d', $seq), 'city' => $base['city'], 'address' => $base['address'], ]; } $patients[] = Patient::withTrashed()->updateOrCreate( [ 'organization_id' => $organization->id, 'patient_number' => $number, ], [ 'uuid' => $this->demoUuid("patient|{$ownerRef}|{$i}"), 'owner_ref' => $ownerRef, 'branch_id' => $branch->id, 'first_name' => $person['first_name'], 'last_name' => $person['last_name'], 'gender' => $person['gender'], 'date_of_birth' => $person['dob'], 'phone' => $person['phone'], 'email' => $person['email'], 'national_id' => $person['national_id'], 'city' => $person['city'], 'region' => 'Greater Accra', 'address' => $person['address'], 'deleted_at' => null, ], ); } return $patients; } /** * @param list $branches * @param array> $departments * @param list $practitioners * @param list $patients * @param array $volumes */ private function seedAppointmentsAndClinical( Organization $organization, array $branches, array $departments, array $practitioners, array $patients, string $ownerRef, array $volumes, ): int { $statuses = [ Appointment::STATUS_SCHEDULED, Appointment::STATUS_CHECKED_IN, Appointment::STATUS_WAITING, Appointment::STATUS_COMPLETED, Appointment::STATUS_COMPLETED, Appointment::STATUS_CANCELLED, ]; $reasons = ['Follow-up', 'New complaint', 'Review labs', 'Chronic care']; $symptoms = ['Fever', 'Cough', 'Headache', 'Fatigue']; $count = 0; for ($i = 0; $i < $volumes['appointments']; $i++) { $patient = $patients[$i % count($patients)]; $practitioner = $practitioners[$i % count($practitioners)]; $branch = $branches[$i % count($branches)]; $branchDepts = $departments[$branch->id] ?? []; $department = $branchDepts[$i % max(1, count($branchDepts))] ?? null; $status = $statuses[$i % count($statuses)]; $scheduledAt = now()->subDays(14)->addHours($i * 3); $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}|{$i}")], [ '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}|{$i}")], [ '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' => $i % 5 === 0 ? Appointment::TYPE_WALK_IN : 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' => $status === Appointment::STATUS_CANCELLED ? $scheduledAt->copy()->subHour() : null, 'reason' => $reasons[$i % count($reasons)], 'created_by' => $ownerRef, 'deleted_at' => null, ], ); if ($visit && $status === Appointment::STATUS_COMPLETED) { $consultation = Consultation::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("consult|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'visit_id' => $visit->id, 'appointment_id' => null, 'practitioner_id' => $practitioner->id, 'patient_id' => $patient->id, 'status' => Consultation::STATUS_COMPLETED, 'symptoms' => $symptoms[$i % count($symptoms)], 'clinical_notes' => 'Demo consultation notes.', 'started_at' => $scheduledAt->copy()->addMinutes(15), 'completed_at' => $scheduledAt->copy()->addHour(), 'completed_by' => $ownerRef, 'deleted_at' => null, ], ); VitalSign::query()->updateOrCreate( [ 'consultation_id' => $consultation->id, 'recorded_at' => $scheduledAt->copy()->addMinutes(10), ], [ 'owner_ref' => $ownerRef, 'bp_systolic' => 110 + ($i % 30), 'bp_diastolic' => 70 + ($i % 15), 'pulse' => 60 + ($i % 40), 'temperature' => 36.5 + (($i % 10) / 10), 'weight_kg' => 55 + ($i % 40), 'height_cm' => 150 + ($i % 40), 'spo2' => 95 + ($i % 5), 'respiratory_rate' => 14 + ($i % 6), 'recorded_by' => $ownerRef, ], ); } $count++; } return $count; } /** * @param list $branches * @param list $practitioners * @param list $patients * @param array $volumes */ private function seedPaidModules( Organization $organization, array $branches, array $practitioners, array $patients, string $ownerRef, array $volumes, ): void { $types = []; foreach ([ ['name' => 'Fasting Blood Glucose', 'code' => 'FBG', 'category' => 'blood', 'unit' => 'mmol/L', 'low' => 3.9, 'high' => 6.1, 'price' => 2500], ['name' => 'Full Blood Count', 'code' => 'FBC', 'category' => 'blood', 'unit' => null, 'low' => null, 'high' => null, 'price' => 4500], ['name' => 'Malaria Parasite', 'code' => 'MP', 'category' => 'blood', 'unit' => null, 'low' => null, 'high' => null, 'price' => 1500], ['name' => 'Urinalysis', 'code' => 'UA', 'category' => 'urine', 'unit' => null, 'low' => null, 'high' => null, 'price' => 2000], ] as $i => $spec) { $types[] = InvestigationType::withTrashed()->updateOrCreate( [ 'organization_id' => $organization->id, 'code' => $spec['code'], ], [ 'owner_ref' => $ownerRef, 'name' => $spec['name'], 'category' => $spec['category'], 'unit' => $spec['unit'], 'reference_low' => $spec['low'], 'reference_high' => $spec['high'], 'price_minor' => $spec['price'], 'is_active' => true, 'deleted_at' => null, ], ); } $drugs = []; foreach ([ ['name' => 'Amoxicillin 500mg', 'unit' => 'capsule', 'price' => 1500], ['name' => 'Paracetamol 500mg', 'unit' => 'tablet', 'price' => 50], ['name' => 'Metformin 500mg', 'unit' => 'tablet', 'price' => 80], ['name' => 'Amlodipine 5mg', 'unit' => 'tablet', 'price' => 120], ] as $i => $spec) { $drug = Drug::withTrashed()->updateOrCreate( [ 'organization_id' => $organization->id, 'sku' => 'DEMO-DRUG-'.($i + 1), ], [ 'owner_ref' => $ownerRef, 'branch_id' => $branches[0]->id, 'name' => $spec['name'], 'generic_name' => $spec['name'], 'unit' => $spec['unit'], 'unit_price_minor' => $spec['price'], 'reorder_level' => 20, 'is_active' => true, 'deleted_at' => null, ], ); DrugBatch::query()->updateOrCreate( [ 'drug_id' => $drug->id, 'batch_number' => 'DEMO-BATCH-'.($i + 1), ], [ 'owner_ref' => $ownerRef, 'expiry_date' => now()->addYear()->toDateString(), 'quantity_on_hand' => 200, 'cost_minor' => (int) ($spec['price'] * 0.6), 'received_at' => now()->subDays(10), ], ); $drugs[] = $drug; } for ($i = 0; $i < $volumes['lab_requests']; $i++) { $patient = $patients[$i % count($patients)]; $branch = $branches[$i % count($branches)]; $practitioner = $practitioners[$i % count($practitioners)]; $type = $types[$i % count($types)]; $visit = Visit::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("lab-visit|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'patient_id' => $patient->id, 'status' => Visit::STATUS_IN_PROGRESS, 'checked_in_at' => now()->subDays($i % 10), 'checked_in_by' => $ownerRef, 'deleted_at' => null, ], ); $consultation = Consultation::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("lab-consult|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'visit_id' => $visit->id, 'practitioner_id' => $practitioner->id, 'patient_id' => $patient->id, 'status' => Consultation::STATUS_DRAFT, 'started_at' => now()->subDays($i % 10), 'deleted_at' => null, ], ); InvestigationRequest::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("lab-req|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'visit_id' => $visit->id, 'consultation_id' => $consultation->id, 'patient_id' => $patient->id, 'investigation_type_id' => $type->id, 'practitioner_id' => $practitioner->id, 'status' => InvestigationRequest::STATUS_PENDING, 'priority' => 'routine', 'clinical_notes' => 'Demo lab request', 'requested_by' => $ownerRef, 'deleted_at' => null, ], ); } for ($i = 0; $i < $volumes['prescriptions']; $i++) { $patient = $patients[$i % count($patients)]; $branch = $branches[$i % count($branches)]; $practitioner = $practitioners[$i % count($practitioners)]; $drug = $drugs[$i % count($drugs)]; $visit = Visit::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("rx-visit|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'patient_id' => $patient->id, 'status' => Visit::STATUS_IN_PROGRESS, 'checked_in_at' => now()->subDays($i % 7), 'checked_in_by' => $ownerRef, 'deleted_at' => null, ], ); $consultation = Consultation::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("rx-consult|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'visit_id' => $visit->id, 'practitioner_id' => $practitioner->id, 'patient_id' => $patient->id, 'status' => Consultation::STATUS_DRAFT, 'started_at' => now()->subDays($i % 7), 'deleted_at' => null, ], ); $prescription = Prescription::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("rx|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'visit_id' => $visit->id, 'consultation_id' => $consultation->id, 'patient_id' => $patient->id, 'practitioner_id' => $practitioner->id, 'status' => Prescription::STATUS_ACTIVE, 'prescribed_by' => $ownerRef, 'deleted_at' => null, ], ); $prescription->items()->updateOrCreate( [ 'prescription_id' => $prescription->id, 'name' => $drug->name, ], [ 'owner_ref' => $ownerRef, 'drug_id' => $drug->id, 'dosage' => '1 '.$drug->unit, 'frequency' => 'BD', 'duration' => '5 days', 'quantity' => 10, 'sort_order' => 0, ], ); } for ($i = 0; $i < $volumes['bills']; $i++) { $patient = $patients[$i % count($patients)]; $branch = $branches[$i % count($branches)]; $fee = 5000 + (($i % 5) * 1000); $visit = Visit::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("bill-visit|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'patient_id' => $patient->id, 'status' => Visit::STATUS_COMPLETED, 'checked_in_at' => now()->subDays($i % 12), 'completed_at' => now()->subDays($i % 12)->addHour(), 'checked_in_by' => $ownerRef, 'deleted_at' => null, ], ); $bill = Bill::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("bill|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'visit_id' => $visit->id, 'patient_id' => $patient->id, 'invoice_number' => sprintf('DEMO-INV-%s-%04d', Str::upper(Str::substr(md5($ownerRef), 0, 4)), $i + 1), 'status' => $i % 3 === 0 ? Bill::STATUS_PAID : Bill::STATUS_OPEN, 'subtotal_minor' => $fee, 'discount_minor' => 0, 'tax_minor' => 0, 'total_minor' => $fee, 'amount_paid_minor' => $i % 3 === 0 ? $fee : 0, 'balance_minor' => $i % 3 === 0 ? 0 : $fee, 'created_by' => $ownerRef, 'finalized_at' => now()->subDays($i % 12), 'deleted_at' => null, ], ); BillLineItem::query()->updateOrCreate( [ 'bill_id' => $bill->id, 'description' => 'Consultation fee', ], [ 'owner_ref' => $ownerRef, 'type' => 'consultation', 'quantity' => 1, 'unit_price_minor' => $fee, 'total_minor' => $fee, ], ); if ($bill->status === Bill::STATUS_PAID) { Payment::query()->updateOrCreate( ['uuid' => $this->demoUuid("pay|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'bill_id' => $bill->id, 'amount_minor' => $fee, 'method' => Payment::METHOD_CASH, 'status' => Payment::STATUS_PAID, 'reference' => 'DEMO-PAY-'.($i + 1), 'paid_at' => now()->subDays($i % 12), 'recorded_by' => $ownerRef, ], ); } } } /** * @param list $branches * @param list $practitioners * @param list $patients */ private function seedAssessments( Organization $organization, array $branches, array $practitioners, array $patients, string $ownerRef, int $count, ): void { $template = AssessmentTemplate::withTrashed()->updateOrCreate( [ 'code' => self::DEMO_TEMPLATE_CODE, 'version' => 1, ], [ 'organization_id' => $organization->id, 'name' => 'Demo Triage Screen', 'category' => AssessmentTemplate::CATEGORY_SCREENING, 'description' => 'Lightweight demo assessment for enterprise analytics.', 'is_current' => true, 'is_active' => true, 'scoring_strategy' => 'sum_items', 'deleted_at' => null, ], ); $question = AssessmentQuestion::query()->updateOrCreate( [ 'template_id' => $template->id, 'code' => 'severity', ], [ 'section' => 'Triage', 'label' => 'Severity (0-10)', 'answer_type' => AssessmentQuestion::TYPE_SCALE, 'is_required' => true, 'sort_order' => 1, 'score_key' => 'severity', 'options' => ['min' => 0, 'max' => 10], ], ); for ($i = 0; $i < $count; $i++) { $patient = $patients[$i % count($patients)]; $branch = $branches[$i % count($branches)]; $practitioner = $practitioners[$i % count($practitioners)]; $scoreValue = (float) ($i % 11); $assessment = Assessment::withTrashed()->updateOrCreate( ['uuid' => $this->demoUuid("assess|{$ownerRef}|{$i}")], [ 'owner_ref' => $ownerRef, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'patient_id' => $patient->id, 'template_id' => $template->id, 'practitioner_id' => $practitioner->id, 'status' => Assessment::STATUS_COMPLETED, 'assessed_at' => now()->subDays($i % 30), 'completed_at' => now()->subDays($i % 30), 'completed_by' => $ownerRef, 'started_by' => $ownerRef, 'notes' => 'Demo assessment', 'deleted_at' => null, ], ); AssessmentAnswer::query()->updateOrCreate( [ 'assessment_id' => $assessment->id, 'question_id' => $question->id, ], [ 'owner_ref' => $ownerRef, 'value_number' => $scoreValue, ], ); AssessmentScore::query()->updateOrCreate( ['assessment_id' => $assessment->id], [ 'owner_ref' => $ownerRef, 'template_code' => self::DEMO_TEMPLATE_CODE, 'total_score' => $scoreValue, 'max_score' => 10, 'subscores' => ['severity' => $scoreValue], 'severity_label' => $scoreValue >= 7 ? 'high' : ($scoreValue >= 4 ? 'moderate' : 'low'), 'computed_at' => now()->subDays($i % 30), ], ); } } private function demoUuid(string $key): string { $hex = md5('ladill-care-demo|'.$key); return sprintf( '%s-%s-%s-%s-%s', substr($hex, 0, 8), substr($hex, 8, 4), substr($hex, 12, 4), substr($hex, 16, 4), substr($hex, 20, 12), ); } }