assertTrue($registry->has('banking')); $this->assertTrue($registry->has('vehicle')); $this->assertTrue($registry->has('restaurant')); $this->assertTrue($registry->has('custom')); $this->assertSame('Banking', $registry->get('banking')->label()); $this->assertSame('customer', $registry->get('banking')->ticketEntity()); $this->assertSame('vehicle', $registry->get('vehicle')->ticketEntity()); $this->assertSame('order', $registry->get('restaurant')->ticketEntity()); } public function test_onboarding_banking_provisions_stages_and_workflow(): void { $user = User::create([ 'public_id' => 'pkg-bank-owner', 'name' => 'Bank Owner', 'email' => 'bank-owner@example.com', 'password' => bcrypt('password'), ]); $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Ridge Bank', 'industry' => 'banking', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main Branch', 'timezone' => 'UTC', ]); $this->assertSame('banking', data_get($org->settings, 'industry')); $this->assertSame('banking', data_get($org->settings, 'industry_package.key')); $this->assertSame('customer', data_get($org->settings, 'industry_package.ticket_entity')); $this->assertSame('Rush', \App\Services\Qms\TicketPriorities::forIndustryKey('restaurant')['emergency']); $this->assertSame('Reservation', \App\Services\Qms\TicketPriorities::forIndustryKey('restaurant')['appointment']); $this->assertSame('Urgent', \App\Services\Qms\TicketPriorities::forIndustryKey('banking')['emergency']); $this->assertSame( 'Priority customer', \App\Services\Qms\TicketPriorities::label($org, 'vip') ); $branch = Branch::where('organization_id', $org->id)->first(); $this->assertNotNull($branch); $queues = ServiceQueue::where('organization_id', $org->id)->where('branch_id', $branch->id)->get(); $this->assertGreaterThanOrEqual(3, $queues->count()); $this->assertTrue($queues->contains(fn (ServiceQueue $q) => $q->prefix === 'T')); $teller = $queues->firstWhere('prefix', 'T'); $this->assertSame('shared_pool', $teller->routingMode()); $this->assertGreaterThanOrEqual(1, $teller->counters()->count()); $workflow = Workflow::where('organization_id', $org->id)->where('industry_template', 'banking')->first(); $this->assertNotNull($workflow); $this->assertGreaterThanOrEqual(3, $workflow->steps()->count()); } public function test_vehicle_uses_assigned_only_for_mechanic(): void { $user = User::create([ 'public_id' => 'pkg-vehicle-owner', 'name' => 'Garage Owner', 'email' => 'garage-owner@example.com', 'password' => bcrypt('password'), ]); // Pro plan so all vehicle stages fit under queue caps. $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Ridge Motors', 'industry' => 'custom', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); $org->update([ 'settings' => array_merge($org->settings ?? [], [ 'plan' => 'pro', 'plan_expires_at' => now()->addMonth()->toIso8601String(), ]), ]); $branch = Branch::where('organization_id', $org->id)->first(); $result = app(IndustryPackageInstaller::class)->install($org->fresh(), $branch, 'vehicle'); $this->assertSame('vehicle', $result['package']); $this->assertGreaterThanOrEqual(5, $result['stages']); $mechanic = ServiceQueue::query() ->where('organization_id', $org->id) ->where('settings->stage_code', 'mechanic') ->first(); $this->assertNotNull($mechanic); $this->assertSame('assigned_only', $mechanic->routingMode()); $this->assertSame('M', $mechanic->prefix); } public function test_package_install_is_idempotent(): void { $user = User::create([ 'public_id' => 'pkg-idem-owner', 'name' => 'Idem Owner', 'email' => 'idem@example.com', 'password' => bcrypt('password'), ]); $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Idem Org', 'industry' => 'visitor', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); $branch = Branch::where('organization_id', $org->id)->first(); $installer = app(IndustryPackageInstaller::class); $first = $installer->install($org, $branch, 'visitor'); $second = $installer->install($org->fresh(), $branch, 'visitor'); $this->assertSame($first['stages'], $second['stages']); $this->assertSame( ServiceQueue::where('organization_id', $org->id)->count(), ServiceQueue::where('organization_id', $org->id)->count(), ); $this->assertSame(2, ServiceQueue::where('organization_id', $org->id)->count()); } public function test_industry_announcement_uses_package_copy(): void { $user = User::create([ 'public_id' => 'pkg-announce-owner', 'name' => 'Announce Owner', 'email' => 'announce@example.com', 'password' => bcrypt('password'), ]); $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Kitchen Co', 'industry' => 'restaurant', 'appointment_mode' => 'walk_in_only', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); $queue = ServiceQueue::where('organization_id', $org->id)->where('prefix', 'U')->first() ?? ServiceQueue::where('organization_id', $org->id)->first(); $counter = $queue->counters()->first() ?? Counter::where('organization_id', $org->id)->first(); $ticket = app(QueueEngine::class)->issueTicket($queue, $user->public_id, [ 'source' => 'api', 'customer_name' => 'Table 4', ]); $announcement = app(VoiceAnnouncementService::class)->announceCalled($ticket->fresh(['organization', 'serviceQueue']), $counter); $this->assertStringContainsString($ticket->ticket_number, $announcement->message); $this->assertTrue( str_contains(strtolower($announcement->message), 'order') || str_contains(strtolower($announcement->message), 'pickup') || str_contains($announcement->message, $counter->displayDestination()), ); } public function test_replace_install_clears_previous_queues_and_tickets(): void { $user = User::create([ 'public_id' => 'pkg-replace-owner', 'name' => 'Replace Owner', 'email' => 'replace-owner@example.com', 'password' => bcrypt('password'), ]); $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Cafe', 'industry' => 'restaurant', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); $org->update([ 'settings' => array_merge($org->settings ?? [], [ 'plan' => 'pro', 'plan_expires_at' => now()->addMonth()->toIso8601String(), ]), ]); $branch = Branch::where('organization_id', $org->id)->first(); $oldQueue = ServiceQueue::where('organization_id', $org->id)->first(); $this->assertNotNull($oldQueue); app(QueueEngine::class)->issueTicket($oldQueue, $user->public_id, [ 'customer_name' => 'Old Ticket', 'source' => 'reception', ]); $this->assertGreaterThan(0, \App\Models\Ticket::where('organization_id', $org->id)->count()); $result = app(IndustryPackageInstaller::class)->install($org->fresh(), $branch, 'banking', [ 'replace' => true, 'include_optional' => true, ]); $this->assertTrue($result['replaced']); $this->assertSame(0, \App\Models\Ticket::where('organization_id', $org->id)->count()); $this->assertFalse( ServiceQueue::where('organization_id', $org->id)->where('id', $oldQueue->id)->exists() ); $this->assertSame('banking', data_get($org->fresh()->settings, 'industry_package.key')); $this->assertNull(data_get($org->fresh()->settings, 'industry_package_needs_reinstall')); $this->assertGreaterThanOrEqual(3, ServiceQueue::where('organization_id', $org->id)->count()); } public function test_changing_industry_requires_reinstall_without_auto_provision(): void { $this->withoutMiddleware(\App\Http\Middleware\EnsurePlatformSession::class); $user = User::create([ 'public_id' => 'pkg-settings-owner', 'name' => 'Settings Owner', 'email' => 'settings-pkg@example.com', 'password' => bcrypt('password'), ]); $org = app(OrganizationResolver::class)->completeOnboarding($user, [ 'organization_name' => 'Shop', 'industry' => 'restaurant', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); $beforeQueues = ServiceQueue::where('organization_id', $org->id)->count(); $this->actingAs($user) ->put(route('qms.settings.update'), [ 'name' => $org->name, 'timezone' => $org->timezone, 'appointment_mode' => 'hybrid', 'industry' => 'banking', 'notifications_enabled' => '1', ]) ->assertRedirect(); $org->refresh(); $this->assertSame('banking', data_get($org->settings, 'industry')); $this->assertSame('restaurant', data_get($org->settings, 'industry_package.key')); $this->assertTrue((bool) data_get($org->settings, 'industry_package_needs_reinstall')); $this->assertTrue(IndustryPackageInstaller::needsReinstall($org)); $this->assertSame($beforeQueues, ServiceQueue::where('organization_id', $org->id)->count()); $this->actingAs($user) ->get(route('qms.dashboard')) ->assertOk() ->assertSee('Reinstall industry package required'); } }