Restaurant (and other packages) now show Rush, Reservation, VIP table, etc. instead of clinic terms like Emergency and Elderly. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -28,6 +28,7 @@ class QueueRuleController extends Controller
|
|||||||
'queue' => $serviceQueue,
|
'queue' => $serviceQueue,
|
||||||
'rules' => $rules,
|
'rules' => $rules,
|
||||||
'ruleTypes' => config('qms.rule_types'),
|
'ruleTypes' => config('qms.rule_types'),
|
||||||
|
'ticketPriorities' => \App\Services\Qms\TicketPriorities::forOrganization($this->organization($request)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ class TicketController extends Controller
|
|||||||
);
|
);
|
||||||
$selectedQueue = $queues->firstWhere('id', $selectedQueueId) ?? $queues->first();
|
$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
|
public function store(Request $request): RedirectResponse
|
||||||
|
|||||||
@@ -55,6 +55,19 @@ class IndustryPackage
|
|||||||
return (array) ($this->definition['terminology'] ?? []);
|
return (array) ($this->definition['terminology'] ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority keys stay global; labels are industry-specific.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
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<string, string>
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ class IndustryPackageInstaller
|
|||||||
'skipped_reason' => $result['skipped_reason'],
|
'skipped_reason' => $result['skipped_reason'],
|
||||||
'ticket_entity' => $package->ticketEntity(),
|
'ticket_entity' => $package->ticketEntity(),
|
||||||
'terminology' => $package->terminology(),
|
'terminology' => $package->terminology(),
|
||||||
|
'priorities' => $package->priorities(),
|
||||||
'announcement' => $package->announcement(),
|
'announcement' => $package->announcement(),
|
||||||
'display_layout' => $package->displayLayout(),
|
'display_layout' => $package->displayLayout(),
|
||||||
'kpis' => $package->kpis(),
|
'kpis' => $package->kpis(),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Services\Qms;
|
namespace App\Services\Qms;
|
||||||
|
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
|
use App\Services\Qms\TicketPriorities;
|
||||||
|
|
||||||
class TicketPresenter
|
class TicketPresenter
|
||||||
{
|
{
|
||||||
@@ -11,7 +12,7 @@ class TicketPresenter
|
|||||||
*/
|
*/
|
||||||
public static function toArray(Ticket $ticket, bool $includeQr = true): array
|
public static function toArray(Ticket $ticket, bool $includeQr = true): array
|
||||||
{
|
{
|
||||||
$ticket->loadMissing(['serviceQueue', 'counter', 'assignedCounter']);
|
$ticket->loadMissing(['serviceQueue', 'counter', 'assignedCounter', 'organization']);
|
||||||
|
|
||||||
$serving = $ticket->counter;
|
$serving = $ticket->counter;
|
||||||
$assigned = $ticket->assignedCounter;
|
$assigned = $ticket->assignedCounter;
|
||||||
@@ -22,6 +23,7 @@ class TicketPresenter
|
|||||||
'ticket_number' => $ticket->ticket_number,
|
'ticket_number' => $ticket->ticket_number,
|
||||||
'status' => $ticket->status,
|
'status' => $ticket->status,
|
||||||
'priority' => $ticket->priority,
|
'priority' => $ticket->priority,
|
||||||
|
'priority_label' => TicketPriorities::label($ticket->organization, $ticket->priority),
|
||||||
'position' => $ticket->position,
|
'position' => $ticket->position,
|
||||||
'customer_name' => $ticket->customer_name,
|
'customer_name' => $ticket->customer_name,
|
||||||
'customer_phone' => $ticket->customer_phone,
|
'customer_phone' => $ticket->customer_phone,
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Qms;
|
||||||
|
|
||||||
|
use App\Models\Organization;
|
||||||
|
use App\Services\Qms\Industry\IndustryPackageRegistry;
|
||||||
|
|
||||||
|
class TicketPriorities
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, string> 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<string, string>
|
||||||
|
*/
|
||||||
|
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<string, string> $overrides
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -53,6 +53,133 @@ return [
|
|||||||
'walk_in' => 'Walk-in',
|
'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' => [
|
'ticket_statuses' => [
|
||||||
'waiting' => 'Waiting',
|
'waiting' => 'Waiting',
|
||||||
'called' => 'Called',
|
'called' => 'Called',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium">Boost priority to</label>
|
<label class="block text-sm font-medium">Boost priority to</label>
|
||||||
<select name="priority" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
<select name="priority" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||||
@foreach (config('qms.ticket_priorities') as $value => $label)
|
@foreach ($ticketPriorities as $value => $label)
|
||||||
<option value="{{ $value }}">{{ $label }}</option>
|
<option value="{{ $value }}">{{ $label }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium">Priority</label>
|
<label class="block text-sm font-medium">Priority</label>
|
||||||
<select name="priority" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
<select name="priority" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||||
@foreach (config('qms.ticket_priorities') as $value => $label)
|
@foreach ($ticketPriorities as $value => $label)
|
||||||
<option value="{{ $value }}" @selected(old('priority', 'walk_in') === $value)>{{ $label }}</option>
|
<option value="{{ $value }}" @selected(old('priority', 'walk_in') === $value)>{{ $label }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ class IndustryPackageTest extends TestCase
|
|||||||
$this->assertSame('banking', data_get($org->settings, 'industry'));
|
$this->assertSame('banking', data_get($org->settings, 'industry'));
|
||||||
$this->assertSame('banking', data_get($org->settings, 'industry_package.key'));
|
$this->assertSame('banking', data_get($org->settings, 'industry_package.key'));
|
||||||
$this->assertSame('customer', data_get($org->settings, 'industry_package.ticket_entity'));
|
$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();
|
$branch = Branch::where('organization_id', $org->id)->first();
|
||||||
$this->assertNotNull($branch);
|
$this->assertNotNull($branch);
|
||||||
|
|||||||
@@ -131,6 +131,32 @@ class ServicePointRoutingTest extends TestCase
|
|||||||
->assertDontSee('@json');
|
->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
|
public function test_web_issue_without_service_point_returns_validation_error_not_500(): void
|
||||||
{
|
{
|
||||||
[$user, , , $queue] = $this->setUpAssignedConsultation();
|
[$user, , , $queue] = $this->setUpAssignedConsultation();
|
||||||
|
|||||||
Reference in New Issue
Block a user