Org name, branches, ticket customers, and staff Members now come from DemoWorld so Queue continuity matches Care/Frontdesk across the suite. Also resolve organization by owner_ref when metadata.queue.organization_id is missing, and skip reseed-on-login for DemoWorld staff emails. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,6 +8,10 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Reseed Queue demo tenants after a fresh SSO login.
|
||||
*
|
||||
* Only owner demo accounts trigger a full reseed. DemoWorld staff logins
|
||||
* (e.g. receptionist@ladill.com variants) share the owner tenant but must
|
||||
* never reseed mid-demo.
|
||||
*/
|
||||
class DemoLoginReseeder
|
||||
{
|
||||
@@ -18,6 +22,10 @@ class DemoLoginReseeder
|
||||
return;
|
||||
}
|
||||
|
||||
if (\App\Support\DemoWorld::isStaffDemoEmail($user->email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plan = $this->planForEmail($user->email);
|
||||
if ($plan === null) {
|
||||
return;
|
||||
|
||||
+103
-30
@@ -22,6 +22,7 @@ use App\Models\User;
|
||||
use App\Models\VoiceAnnouncement;
|
||||
use App\Models\Workflow;
|
||||
use App\Models\WorkflowStep;
|
||||
use App\Support\DemoWorld;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -74,13 +75,16 @@ class DemoSeeder
|
||||
$this->resetForOwner($user->ownerRef());
|
||||
}
|
||||
|
||||
$organization = $this->ensureOrganization($user);
|
||||
$organization = $this->ensureOrganization($user, $plan);
|
||||
$this->assignPlan($organization, $plan);
|
||||
|
||||
$volumes = self::VOLUMES[$plan];
|
||||
$branches = $this->seedBranches($user->ownerRef(), $organization, $volumes['branches']);
|
||||
$branches = $this->seedBranches($user->ownerRef(), $organization, $plan, $volumes['branches']);
|
||||
$this->seedStaffMembers($user->ownerRef(), $organization, $plan, $branches);
|
||||
$queues = [];
|
||||
$counters = [];
|
||||
$people = DemoWorld::people();
|
||||
$personCursor = 0;
|
||||
|
||||
foreach ($branches as $branchIndex => $branch) {
|
||||
$dept = $this->ensureDepartment($user->ownerRef(), $branch);
|
||||
@@ -104,13 +108,15 @@ class DemoSeeder
|
||||
$this->seedDevice($user->ownerRef(), $organization, $branch);
|
||||
|
||||
foreach ($branchQueues as $qi => $queue) {
|
||||
$this->seedLiveTickets(
|
||||
$personCursor = $this->seedLiveTickets(
|
||||
$user->ownerRef(),
|
||||
$organization,
|
||||
$branch,
|
||||
$queue,
|
||||
$branchCounters[$qi % max(1, count($branchCounters))] ?? null,
|
||||
$volumes['tickets_per_queue'],
|
||||
$people,
|
||||
$personCursor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,6 +140,7 @@ class DemoSeeder
|
||||
$queues[0] ?? null,
|
||||
$counters[0] ?? null,
|
||||
$volumes['history_tickets'],
|
||||
$people,
|
||||
);
|
||||
|
||||
$organization->refresh();
|
||||
@@ -248,51 +255,98 @@ class DemoSeeder
|
||||
});
|
||||
}
|
||||
|
||||
private function ensureOrganization(User $user): Organization
|
||||
private function ensureOrganization(User $user, string $plan): Organization
|
||||
{
|
||||
$business = DemoWorld::business($plan);
|
||||
$existing = $this->orgs->resolveForUser($user);
|
||||
if ($existing) {
|
||||
$settings = $existing->settings ?? [];
|
||||
$settings['onboarded'] = true;
|
||||
$existing->forceFill(['settings' => $settings])->save();
|
||||
$existing->forceFill([
|
||||
'name' => $business['name'],
|
||||
'settings' => $settings,
|
||||
])->save();
|
||||
$this->orgs->ensureOwnerMember($user, $existing);
|
||||
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$hq = DemoWorld::branches($plan, 1)[0];
|
||||
|
||||
return $this->orgs->completeOnboarding($user, [
|
||||
'organization_name' => 'Demo Queue Org',
|
||||
'organization_name' => $business['name'],
|
||||
'industry' => 'retail',
|
||||
'appointment_mode' => 'hybrid',
|
||||
'branch_name' => 'Main Branch',
|
||||
'timezone' => 'Africa/Accra',
|
||||
'branch_name' => $hq['name'],
|
||||
'branch_address' => $hq['address'],
|
||||
'branch_phone' => $hq['phone'],
|
||||
'timezone' => $business['timezone'] ?? 'Africa/Accra',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return list<Branch> */
|
||||
private function seedBranches(string $ownerRef, Organization $organization, int $count): array
|
||||
private function seedBranches(string $ownerRef, Organization $organization, string $plan, int $count): array
|
||||
{
|
||||
$branches = Branch::query()
|
||||
$catalog = DemoWorld::branches($plan, $count);
|
||||
$existing = Branch::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->all();
|
||||
|
||||
$names = ['Main Branch', 'East Legon', 'Kumasi Central', 'Tema Harbour', 'Takoradi Mall'];
|
||||
for ($i = count($branches); $i < $count; $i++) {
|
||||
$branches[] = Branch::query()->create([
|
||||
$branches = [];
|
||||
foreach ($catalog as $i => $spec) {
|
||||
$attrs = [
|
||||
'owner_ref' => $ownerRef,
|
||||
'organization_id' => $organization->id,
|
||||
'name' => $names[$i] ?? ('Branch '.($i + 1)),
|
||||
'code' => 'BR'.($i + 1),
|
||||
'address' => 'Demo address '.($i + 1).', Accra',
|
||||
'phone' => '0302'.str_pad((string) (100000 + $i), 6, '0', STR_PAD_LEFT),
|
||||
'name' => $spec['name'],
|
||||
'code' => $spec['code'],
|
||||
'address' => $spec['address'],
|
||||
'phone' => $spec['phone'],
|
||||
'is_active' => true,
|
||||
]);
|
||||
];
|
||||
|
||||
$branch = $existing[$i] ?? null;
|
||||
if ($branch) {
|
||||
$branch->fill($attrs)->save();
|
||||
} else {
|
||||
$branch = Branch::query()->create($attrs);
|
||||
}
|
||||
|
||||
$branches[] = $branch;
|
||||
}
|
||||
|
||||
return array_slice($branches, 0, $count);
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Member rows for DemoWorld staff with a queue role (user_ref = email
|
||||
* until SSO remaps to public_id).
|
||||
*
|
||||
* @param list<Branch> $branches
|
||||
*/
|
||||
private function seedStaffMembers(string $ownerRef, Organization $organization, string $plan, array $branches): void
|
||||
{
|
||||
$hq = $branches[0] ?? null;
|
||||
foreach (DemoWorld::staff($plan) as $staff) {
|
||||
$role = $staff['roles']['queue'] ?? 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' => $hq?->id,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureDepartment(string $ownerRef, Branch $branch): Department
|
||||
@@ -426,6 +480,10 @@ class DemoSeeder
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{key: string, first_name: string, last_name: string, phone: string}> $people
|
||||
* @return int next person cursor for the caller to continue from
|
||||
*/
|
||||
private function seedLiveTickets(
|
||||
string $ownerRef,
|
||||
Organization $organization,
|
||||
@@ -433,16 +491,22 @@ class DemoSeeder
|
||||
ServiceQueue $queue,
|
||||
?Counter $counter,
|
||||
int $count,
|
||||
): void {
|
||||
array $people,
|
||||
int $personCursor,
|
||||
): int {
|
||||
$existing = Ticket::query()
|
||||
->where('service_queue_id', $queue->id)
|
||||
->whereIn('status', ['waiting', 'called', 'serving'])
|
||||
->count();
|
||||
|
||||
$storyStart = DemoWorld::storyDay()->copy()->addHours(9);
|
||||
|
||||
for ($i = $existing; $i < $count; $i++) {
|
||||
$seq = $queue->ticket_sequence + 1;
|
||||
$queue->forceFill(['ticket_sequence' => $seq])->save();
|
||||
$status = ['waiting', 'waiting', 'called', 'serving'][$i % 4];
|
||||
$person = $people[$personCursor % count($people)];
|
||||
$personCursor++;
|
||||
|
||||
Ticket::query()->create([
|
||||
'owner_ref' => $ownerRef,
|
||||
@@ -454,16 +518,19 @@ class DemoSeeder
|
||||
'status' => $status,
|
||||
'priority' => ['walk_in', 'vip', 'elderly', 'appointment'][$i % 4],
|
||||
'position' => $i + 1,
|
||||
'customer_name' => 'Guest '.($i + 1),
|
||||
'customer_phone' => '024'.str_pad((string) (3000000 + $i), 7, '0', STR_PAD_LEFT),
|
||||
'customer_name' => DemoWorld::fullName($person),
|
||||
'customer_phone' => $person['phone'],
|
||||
'source' => ['kiosk', 'reception', 'web'][$i % 3],
|
||||
'issued_at' => now()->subMinutes(30 - $i),
|
||||
'called_at' => in_array($status, ['called', 'serving'], true) ? now()->subMinutes(5) : null,
|
||||
'serving_started_at' => $status === 'serving' ? now()->subMinutes(2) : null,
|
||||
'issued_at' => $storyStart->copy()->addMinutes($i * 3),
|
||||
'called_at' => in_array($status, ['called', 'serving'], true) ? $storyStart->copy()->addMinutes($i * 3 + 5) : null,
|
||||
'serving_started_at' => $status === 'serving' ? $storyStart->copy()->addMinutes($i * 3 + 7) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $personCursor;
|
||||
}
|
||||
|
||||
/** @param list<array{key: string, first_name: string, last_name: string, phone: string}> $people */
|
||||
private function seedHistoryTickets(
|
||||
string $ownerRef,
|
||||
Organization $organization,
|
||||
@@ -471,6 +538,7 @@ class DemoSeeder
|
||||
?ServiceQueue $queue,
|
||||
?Counter $counter,
|
||||
int $count,
|
||||
array $people,
|
||||
): void {
|
||||
if (! $queue) {
|
||||
return;
|
||||
@@ -481,10 +549,14 @@ class DemoSeeder
|
||||
->whereIn('status', ['completed', 'no_show', 'cancelled'])
|
||||
->count();
|
||||
|
||||
$storyDay = DemoWorld::storyDay();
|
||||
|
||||
for ($i = $existing; $i < $count; $i++) {
|
||||
$seq = $queue->ticket_sequence + 1;
|
||||
$queue->forceFill(['ticket_sequence' => $seq])->save();
|
||||
$status = ['completed', 'completed', 'completed', 'no_show', 'cancelled'][$i % 5];
|
||||
$person = $people[$i % count($people)];
|
||||
$day = $storyDay->copy()->subDays(($i % 14) + 1);
|
||||
|
||||
$ticket = Ticket::query()->create([
|
||||
'owner_ref' => $ownerRef,
|
||||
@@ -495,12 +567,13 @@ class DemoSeeder
|
||||
'ticket_number' => $queue->prefix.str_pad((string) $seq, 3, '0', STR_PAD_LEFT),
|
||||
'status' => $status,
|
||||
'priority' => 'walk_in',
|
||||
'customer_name' => 'History Guest '.($i + 1),
|
||||
'customer_name' => DemoWorld::fullName($person),
|
||||
'customer_phone' => $person['phone'],
|
||||
'source' => 'kiosk',
|
||||
'issued_at' => now()->subDays(($i % 14) + 1)->subMinutes(40),
|
||||
'called_at' => now()->subDays(($i % 14) + 1)->subMinutes(20),
|
||||
'serving_started_at' => $status === 'completed' ? now()->subDays(($i % 14) + 1)->subMinutes(15) : null,
|
||||
'completed_at' => $status === 'completed' ? now()->subDays(($i % 14) + 1)->subMinutes(5) : null,
|
||||
'issued_at' => $day->copy()->subMinutes(40),
|
||||
'called_at' => $day->copy()->subMinutes(20),
|
||||
'serving_started_at' => $status === 'completed' ? $day->copy()->subMinutes(15) : null,
|
||||
'completed_at' => $status === 'completed' ? $day->copy()->subMinutes(5) : null,
|
||||
]);
|
||||
|
||||
TicketEvent::query()->create([
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Qms;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Services\Identity\IdentityTeamClient;
|
||||
|
||||
@@ -35,7 +36,11 @@ class TeamMemberProvisioner
|
||||
}
|
||||
|
||||
$meta = (array) data_get($grant, 'metadata.queue', []);
|
||||
$ownerRef = (string) ($grant['account'] ?? '');
|
||||
$organizationId = (int) ($meta['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) ($meta['role'] ?? 'member'),
|
||||
'branch_id' => $meta['branch_id'] ?? null,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user