Seed Care demos from shared DemoWorld with staff roles.
Deploy Ladill Care / deploy (push) Successful in 1m0s

Patients, branches, and org identity now match suite continuity keys; staff Members are created for training SSO without login reseed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-17 13:27:07 +00:00
co-authored by Cursor
parent 2d548e2bbc
commit dd4efec135
6 changed files with 689 additions and 19 deletions
+8
View File
@@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Log;
/**
* Reseed Care demo tenants after a fresh SSO login.
*
* Only owner demo accounts (and legacy enterprise org members) trigger a full
* reseed. Staff training logins never wipe mid-demo.
*/
class DemoLoginReseeder
{
@@ -18,6 +21,11 @@ class DemoLoginReseeder
return;
}
// Staff demos share the owner tenant but must not reseed on login.
if (\App\Support\DemoWorld::isStaffDemoEmail($user->email)) {
return;
}
$plan = $this->planForEmail($user->email);
if ($plan === null) {
return;
+65 -18
View File
@@ -26,6 +26,7 @@ use App\Models\Prescription;
use App\Models\User;
use App\Models\Visit;
use App\Models\VitalSign;
use App\Support\DemoWorld;
use Faker\Factory as FakerFactory;
use Faker\Generator as Faker;
use Illuminate\Support\Facades\DB;
@@ -103,6 +104,7 @@ class DemoTenantSeeder
$this->upsertOwnerMember($organization, $ownerRef);
$branches = $this->seedBranches($organization, $ownerRef, $volumes, $faker);
$this->seedStaffMembers($organization, $ownerRef, $plan, $branches);
$departments = $this->seedDepartments($branches, $ownerRef, $volumes);
$practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes, $faker);
$patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes, $faker);
@@ -298,11 +300,7 @@ class DemoTenantSeeder
$slug = 'demo-'.Str::lower(Str::substr($ownerRef, 0, 8));
}
$name = match ($plan) {
'enterprise' => 'Accra Healthcare Group (Care Demo)',
'pro' => 'Ladill Care Demo (Pro)',
default => 'Ladill Care Demo (Free)',
};
$name = DemoWorld::business($plan)['name'];
$organization = Organization::withTrashed()
->where('owner_ref', $ownerRef)
@@ -361,9 +359,12 @@ class DemoTenantSeeder
*/
private function seedBranches(Organization $organization, string $ownerRef, array $volumes, Faker $faker): array
{
$catalog = DemoWorld::branches($this->normalizePlan(
(string) data_get($organization->settings, 'plan', 'free')
), $volumes['branches']);
$branches = [];
for ($i = 0; $i < $volumes['branches']; $i++) {
$code = sprintf('DEMO-BR-%02d', $i + 1);
foreach ($catalog as $i => $spec) {
$code = $spec['code'] !== '' ? $spec['code'] : sprintf('DEMO-BR-%02d', $i + 1);
$branches[] = Branch::withTrashed()->updateOrCreate(
[
'organization_id' => $organization->id,
@@ -371,9 +372,9 @@ class DemoTenantSeeder
],
[
'owner_ref' => $ownerRef,
'name' => self::BRANCH_NAMES[$i % count(self::BRANCH_NAMES)],
'address' => $faker->streetAddress().', Accra',
'phone' => '+23320'.str_pad((string) (1000000 + $i), 7, '0', STR_PAD_LEFT),
'name' => $spec['name'],
'address' => $spec['address'],
'phone' => $spec['phone'],
'is_active' => true,
'deleted_at' => null,
],
@@ -383,6 +384,34 @@ class DemoTenantSeeder
return $branches;
}
/**
* Local Member rows for DemoWorld staff (user_ref = email until SSO remaps to public_id).
*
* @param list<Branch> $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<Branch> $branches
* @param array<string, mixed> $volumes
@@ -473,10 +502,27 @@ class DemoTenantSeeder
array $volumes,
Faker $faker,
): 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(
[
@@ -487,15 +533,16 @@ class DemoTenantSeeder
'uuid' => $this->demoUuid("patient|{$ownerRef}|{$i}"),
'owner_ref' => $ownerRef,
'branch_id' => $branch->id,
'first_name' => self::FIRST_NAMES[$i % count(self::FIRST_NAMES)],
'last_name' => self::LAST_NAMES[($i * 3) % count(self::LAST_NAMES)],
'gender' => $i % 2 === 0 ? 'female' : 'male',
'date_of_birth' => now()->subYears(18 + ($i % 50))->subDays($i % 28)->toDateString(),
'phone' => '+23324'.str_pad((string) (2000000 + $i), 7, '0', STR_PAD_LEFT),
'email' => sprintf('demo.patient.%d@example.test', $i + 1),
'city' => 'Accra',
'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' => $faker->streetAddress(),
'address' => $person['address'],
'deleted_at' => null,
],
);
+6 -1
View File
@@ -3,6 +3,7 @@
namespace App\Services\Care;
use App\Models\Member;
use App\Models\Organization;
use App\Models\User;
use App\Services\Identity\IdentityTeamClient;
@@ -35,7 +36,11 @@ class TeamMemberProvisioner
}
$appMeta = (array) data_get($grant, 'metadata.care', []);
$ownerRef = (string) ($grant['account'] ?? '');
$organizationId = (int) ($appMeta['organization_id'] ?? 0);
if ($organizationId <= 0 && $ownerRef !== '') {
$organizationId = (int) Organization::query()->where('owner_ref', $ownerRef)->value('id');
}
if ($organizationId <= 0) {
continue;
}
@@ -46,7 +51,7 @@ class TeamMemberProvisioner
'user_ref' => $user->ownerRef(),
],
[
'owner_ref' => (string) ($grant['account'] ?? $user->ownerRef()),
'owner_ref' => $ownerRef !== '' ? $ownerRef : $user->ownerRef(),
'role' => (string) ($appMeta['role'] ?? 'member'),
'branch_id' => $appMeta['branch_id'] ?? null,
],