From b5ffa499b9c6d445424c8852ecda426e1d46594c Mon Sep 17 00:00:00 2001 From: isaacclad Date: Fri, 17 Jul 2026 20:15:56 +0000 Subject: [PATCH] Use industry-specific ticket priority labels. Restaurant (and other packages) now show Rush, Reservation, VIP table, etc. instead of clinic terms like Emergency and Elderly. Co-authored-by: Cursor --- .../Controllers/Qms/QueueRuleController.php | 1 + app/Http/Controllers/Qms/TicketController.php | 7 +- app/Services/Qms/Industry/IndustryPackage.php | 13 ++ .../Qms/Industry/IndustryPackageInstaller.php | 1 + app/Services/Qms/TicketPresenter.php | 4 +- app/Services/Qms/TicketPriorities.php | 66 +++++++++ config/qms.php | 127 ++++++++++++++++++ resources/views/qms/rules/index.blade.php | 2 +- resources/views/qms/tickets/create.blade.php | 2 +- tests/Feature/IndustryPackageTest.php | 7 + tests/Feature/ServicePointRoutingTest.php | 26 ++++ 11 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 app/Services/Qms/TicketPriorities.php diff --git a/app/Http/Controllers/Qms/QueueRuleController.php b/app/Http/Controllers/Qms/QueueRuleController.php index 9477d34..b241a69 100644 --- a/app/Http/Controllers/Qms/QueueRuleController.php +++ b/app/Http/Controllers/Qms/QueueRuleController.php @@ -28,6 +28,7 @@ class QueueRuleController extends Controller 'queue' => $serviceQueue, 'rules' => $rules, 'ruleTypes' => config('qms.rule_types'), + 'ticketPriorities' => \App\Services\Qms\TicketPriorities::forOrganization($this->organization($request)), ]); } diff --git a/app/Http/Controllers/Qms/TicketController.php b/app/Http/Controllers/Qms/TicketController.php index 7b8b7d5..f2bea07 100644 --- a/app/Http/Controllers/Qms/TicketController.php +++ b/app/Http/Controllers/Qms/TicketController.php @@ -73,7 +73,12 @@ class TicketController extends Controller ); $selectedQueue = $queues->firstWhere('id', $selectedQueueId) ?? $queues->first(); - return view('qms.tickets.create', compact('queues', 'organization', 'selectedQueue')); + return view('qms.tickets.create', [ + 'queues' => $queues, + 'organization' => $organization, + 'selectedQueue' => $selectedQueue, + 'ticketPriorities' => \App\Services\Qms\TicketPriorities::forOrganization($organization), + ]); } public function store(Request $request): RedirectResponse diff --git a/app/Services/Qms/Industry/IndustryPackage.php b/app/Services/Qms/Industry/IndustryPackage.php index 0f92f43..d3f41d9 100644 --- a/app/Services/Qms/Industry/IndustryPackage.php +++ b/app/Services/Qms/Industry/IndustryPackage.php @@ -55,6 +55,19 @@ class IndustryPackage return (array) ($this->definition['terminology'] ?? []); } + /** + * Priority keys stay global; labels are industry-specific. + * + * @return array + */ + public function priorities(): array + { + $fromPackage = (array) ($this->definition['priorities'] ?? []); + $fromIndustry = (array) config("qms.ticket_priorities_by_industry.{$this->key}", []); + + return \App\Services\Qms\TicketPriorities::mergeDefaults(array_merge($fromIndustry, $fromPackage)); + } + /** * @return array */ diff --git a/app/Services/Qms/Industry/IndustryPackageInstaller.php b/app/Services/Qms/Industry/IndustryPackageInstaller.php index ce69e8b..d4b538d 100644 --- a/app/Services/Qms/Industry/IndustryPackageInstaller.php +++ b/app/Services/Qms/Industry/IndustryPackageInstaller.php @@ -154,6 +154,7 @@ class IndustryPackageInstaller 'skipped_reason' => $result['skipped_reason'], 'ticket_entity' => $package->ticketEntity(), 'terminology' => $package->terminology(), + 'priorities' => $package->priorities(), 'announcement' => $package->announcement(), 'display_layout' => $package->displayLayout(), 'kpis' => $package->kpis(), diff --git a/app/Services/Qms/TicketPresenter.php b/app/Services/Qms/TicketPresenter.php index 29bad24..31c759c 100644 --- a/app/Services/Qms/TicketPresenter.php +++ b/app/Services/Qms/TicketPresenter.php @@ -3,6 +3,7 @@ namespace App\Services\Qms; use App\Models\Ticket; +use App\Services\Qms\TicketPriorities; class TicketPresenter { @@ -11,7 +12,7 @@ class TicketPresenter */ public static function toArray(Ticket $ticket, bool $includeQr = true): array { - $ticket->loadMissing(['serviceQueue', 'counter', 'assignedCounter']); + $ticket->loadMissing(['serviceQueue', 'counter', 'assignedCounter', 'organization']); $serving = $ticket->counter; $assigned = $ticket->assignedCounter; @@ -22,6 +23,7 @@ class TicketPresenter 'ticket_number' => $ticket->ticket_number, 'status' => $ticket->status, 'priority' => $ticket->priority, + 'priority_label' => TicketPriorities::label($ticket->organization, $ticket->priority), 'position' => $ticket->position, 'customer_name' => $ticket->customer_name, 'customer_phone' => $ticket->customer_phone, diff --git a/app/Services/Qms/TicketPriorities.php b/app/Services/Qms/TicketPriorities.php new file mode 100644 index 0000000..60f3184 --- /dev/null +++ b/app/Services/Qms/TicketPriorities.php @@ -0,0 +1,66 @@ + priority key => industry label + */ + public static function forOrganization(?Organization $organization): array + { + $stored = data_get($organization?->settings, 'industry_package.priorities'); + if (is_array($stored) && $stored !== []) { + return self::mergeDefaults($stored); + } + + $key = (string) ( + data_get($organization?->settings, 'industry_package.key') + ?? data_get($organization?->settings, 'industry') + ?? 'custom' + ); + + return self::forIndustryKey($key); + } + + /** + * @return array + */ + public static function forIndustryKey(string $key): array + { + $package = app(IndustryPackageRegistry::class)->get($key); + + return $package?->priorities() ?? self::mergeDefaults( + (array) config("qms.ticket_priorities_by_industry.{$key}", []) + ); + } + + public static function label(?Organization $organization, ?string $priority): string + { + if ($priority === null || $priority === '') { + return ''; + } + + $map = self::forOrganization($organization); + + return $map[$priority] ?? (string) (config("qms.ticket_priorities.{$priority}") ?? $priority); + } + + /** + * @param array $overrides + * @return array + */ + public static function mergeDefaults(array $overrides): array + { + $defaults = (array) config('qms.ticket_priorities', []); + $merged = []; + foreach ($defaults as $key => $label) { + $merged[$key] = (string) ($overrides[$key] ?? $label); + } + + return $merged; + } +} diff --git a/config/qms.php b/config/qms.php index c651cf0..8d101fc 100644 --- a/config/qms.php +++ b/config/qms.php @@ -53,6 +53,133 @@ return [ 'walk_in' => 'Walk-in', ], + /* + | Industry-specific labels for the same priority keys (order stays global). + | Missing keys fall back to ticket_priorities above. + */ + 'ticket_priorities_by_industry' => [ + 'healthcare' => [ + 'emergency' => 'Emergency', + 'vip' => 'VIP', + 'elderly' => 'Elderly', + 'disabled' => 'Disabled', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'restaurant' => [ + 'emergency' => 'Rush', + 'vip' => 'VIP table', + 'elderly' => 'Large party', + 'disabled' => 'Accessibility', + 'appointment' => 'Reservation', + 'walk_in' => 'Walk-in', + ], + 'banking' => [ + 'emergency' => 'Urgent', + 'vip' => 'Priority customer', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'government' => [ + 'emergency' => 'Urgent', + 'vip' => 'Priority', + 'elderly' => 'Senior citizen', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'retail_service' => [ + 'emergency' => 'Urgent', + 'vip' => 'VIP', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'vehicle' => [ + 'emergency' => 'Breakdown', + 'vip' => 'Priority service', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Booked slot', + 'walk_in' => 'Walk-in', + ], + 'hospitality' => [ + 'emergency' => 'Urgent', + 'vip' => 'VIP guest', + 'elderly' => 'Senior guest', + 'disabled' => 'Accessibility', + 'appointment' => 'Reservation', + 'walk_in' => 'Walk-in', + ], + 'logistics' => [ + 'emergency' => 'Express', + 'vip' => 'Priority shipment', + 'elderly' => 'Fragile', + 'disabled' => 'Special handling', + 'appointment' => 'Scheduled pickup', + 'walk_in' => 'Standard', + ], + 'university' => [ + 'emergency' => 'Urgent', + 'vip' => 'Priority', + 'elderly' => 'Accessibility / senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'telecom' => [ + 'emergency' => 'Outage / critical', + 'vip' => 'Priority customer', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'events' => [ + 'emergency' => 'VIP / media', + 'vip' => 'VIP', + 'elderly' => 'Accessibility', + 'disabled' => 'Accessibility', + 'appointment' => 'Pre-registered', + 'walk_in' => 'General admission', + ], + 'church' => [ + 'emergency' => 'Urgent pastoral', + 'vip' => 'Priority', + 'elderly' => 'Elder', + 'disabled' => 'Accessibility', + 'appointment' => 'Appointment', + 'walk_in' => 'Walk-in', + ], + 'visitor' => [ + 'emergency' => 'Urgent', + 'vip' => 'VIP visitor', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Expected visitor', + 'walk_in' => 'Walk-in', + ], + 'retail' => [ + 'emergency' => 'Urgent', + 'vip' => 'VIP', + 'elderly' => 'Senior', + 'disabled' => 'Accessibility', + 'appointment' => 'Click & collect', + 'walk_in' => 'Walk-in', + ], + 'custom' => [ + 'emergency' => 'Urgent', + 'vip' => 'Priority', + 'elderly' => 'Assisted', + 'disabled' => 'Accessibility', + 'appointment' => 'Scheduled', + 'walk_in' => 'Standard', + ], + ], + 'ticket_statuses' => [ 'waiting' => 'Waiting', 'called' => 'Called', diff --git a/resources/views/qms/rules/index.blade.php b/resources/views/qms/rules/index.blade.php index 3618143..7afe73d 100644 --- a/resources/views/qms/rules/index.blade.php +++ b/resources/views/qms/rules/index.blade.php @@ -33,7 +33,7 @@
diff --git a/resources/views/qms/tickets/create.blade.php b/resources/views/qms/tickets/create.blade.php index b25abfb..b36b55a 100644 --- a/resources/views/qms/tickets/create.blade.php +++ b/resources/views/qms/tickets/create.blade.php @@ -66,7 +66,7 @@
diff --git a/tests/Feature/IndustryPackageTest.php b/tests/Feature/IndustryPackageTest.php index c10b5be..e1ebee6 100644 --- a/tests/Feature/IndustryPackageTest.php +++ b/tests/Feature/IndustryPackageTest.php @@ -52,6 +52,13 @@ class IndustryPackageTest extends TestCase $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); diff --git a/tests/Feature/ServicePointRoutingTest.php b/tests/Feature/ServicePointRoutingTest.php index 9197cf7..3a2b187 100644 --- a/tests/Feature/ServicePointRoutingTest.php +++ b/tests/Feature/ServicePointRoutingTest.php @@ -131,6 +131,32 @@ class ServicePointRoutingTest extends TestCase ->assertDontSee('@json'); } + public function test_ticket_priority_labels_follow_industry_package(): void + { + $user = User::create([ + 'public_id' => 'prio-rest-owner', + 'name' => 'Restaurant Owner', + 'email' => 'prio-rest@example.com', + 'password' => bcrypt('password'), + ]); + app(OrganizationResolver::class)->completeOnboarding($user, [ + 'organization_name' => 'Bistro', + 'industry' => 'restaurant', + 'appointment_mode' => 'hybrid', + 'branch_name' => 'Main', + 'timezone' => 'UTC', + ]); + + $this->actingAs($user) + ->get(route('qms.tickets.create')) + ->assertOk() + ->assertSee('Rush') + ->assertSee('Reservation') + ->assertSee('VIP table') + ->assertDontSee('>Emergency<', false) + ->assertDontSee('>Elderly<', false); + } + public function test_web_issue_without_service_point_returns_validation_error_not_500(): void { [$user, , , $queue] = $this->setUpAssignedConsultation();