Add industry packages that provision workflow stages on onboarding.
Deploy Ladill Queue / deploy (push) Successful in 1m39s
Deploy Ladill Queue / deploy (push) Successful in 1m39s
Queue Core stays generic; selecting Healthcare, Banking, Restaurant, and other industries installs departments, stages (queues), service points, workflows, terminology, and announcement profiles without code changes per vertical. Care-linked orgs skip stage materialization so Care remains the clinical provisioner. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Qms\Industry;
|
||||
|
||||
/**
|
||||
* Read-only view of a configured industry package.
|
||||
*
|
||||
* @phpstan-type StageDef array{
|
||||
* code: string,
|
||||
* department: string,
|
||||
* name: string,
|
||||
* prefix: string,
|
||||
* strategy?: string,
|
||||
* routing_mode?: string,
|
||||
* routing_strategy?: string,
|
||||
* optional?: bool,
|
||||
* service_points?: list<array{name: string, destination?: string, code?: string}>
|
||||
* }
|
||||
*/
|
||||
class IndustryPackage
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $definition
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $key,
|
||||
public readonly array $definition,
|
||||
) {}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return (string) ($this->definition['label'] ?? $this->key);
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return (string) ($this->definition['description'] ?? '');
|
||||
}
|
||||
|
||||
public function ticketEntity(): string
|
||||
{
|
||||
return (string) ($this->definition['ticket_entity'] ?? 'customer');
|
||||
}
|
||||
|
||||
public function displayLayout(): string
|
||||
{
|
||||
return (string) ($this->definition['display_layout'] ?? 'standard');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function terminology(): array
|
||||
{
|
||||
return (array) ($this->definition['terminology'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function announcement(): array
|
||||
{
|
||||
return (array) ($this->definition['announcement'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function kpis(): array
|
||||
{
|
||||
return array_values((array) ($this->definition['kpis'] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{name: string, description?: string}
|
||||
*/
|
||||
public function workflowMeta(): array
|
||||
{
|
||||
return (array) ($this->definition['workflow'] ?? ['name' => $this->label().' workflow']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{code: string, name: string, type: string}>
|
||||
*/
|
||||
public function departments(): array
|
||||
{
|
||||
return array_values((array) ($this->definition['departments'] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<StageDef>
|
||||
*/
|
||||
public function stages(): array
|
||||
{
|
||||
return array_values((array) ($this->definition['stages'] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function integrations(): array
|
||||
{
|
||||
return array_values((array) ($this->definition['integrations'] ?? []));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Qms\Industry;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\Counter;
|
||||
use App\Models\Department;
|
||||
use App\Models\Organization;
|
||||
use App\Models\ServiceQueue;
|
||||
use App\Models\Workflow;
|
||||
use App\Models\WorkflowStep;
|
||||
use App\Services\Qms\AuditLogger;
|
||||
use App\Services\Qms\PlanService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Materializes an industry package onto Queue Core entities.
|
||||
*
|
||||
* Stage → ServiceQueue, Service Point → Counter, Worker remains Member/staff_ref.
|
||||
* Idempotent via settings.external_key / departments.external_key.
|
||||
*/
|
||||
class IndustryPackageInstaller
|
||||
{
|
||||
public function __construct(
|
||||
protected IndustryPackageRegistry $registry,
|
||||
protected PlanService $plans,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{skip_stages?: bool, include_optional?: bool, actor_ref?: string|null} $options
|
||||
* @return array{
|
||||
* package: string,
|
||||
* departments: int,
|
||||
* stages: int,
|
||||
* service_points: int,
|
||||
* workflow_id: int|null,
|
||||
* skipped_stages: list<string>,
|
||||
* skipped_reason: string|null
|
||||
* }
|
||||
*/
|
||||
public function install(Organization $organization, Branch $branch, ?string $packageKey = null, array $options = []): array
|
||||
{
|
||||
$key = $this->registry->resolveKey($packageKey ?? data_get($organization->settings, 'industry'));
|
||||
$package = $this->registry->get($key);
|
||||
if (! $package) {
|
||||
$package = $this->registry->get('custom');
|
||||
$key = 'custom';
|
||||
}
|
||||
|
||||
$skipStages = (bool) ($options['skip_stages'] ?? false);
|
||||
$includeOptional = (bool) ($options['include_optional'] ?? true);
|
||||
$actor = $options['actor_ref'] ?? $organization->owner_ref;
|
||||
|
||||
// Care (and similar) own clinical stage provisioning — still apply terminology.
|
||||
if (! $skipStages && data_get($organization->settings, 'integrations.care_enabled') && $key === 'healthcare') {
|
||||
$skipStages = true;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'package' => $key,
|
||||
'departments' => 0,
|
||||
'stages' => 0,
|
||||
'service_points' => 0,
|
||||
'workflow_id' => null,
|
||||
'skipped_stages' => [],
|
||||
'skipped_reason' => $skipStages ? 'stages_managed_by_integration' : null,
|
||||
];
|
||||
|
||||
return DB::transaction(function () use ($organization, $branch, $package, $key, $skipStages, $includeOptional, $actor, &$result) {
|
||||
$this->applyPackageSettings($organization, $package);
|
||||
|
||||
$departmentsByCode = [];
|
||||
foreach ($package->departments() as $deptDef) {
|
||||
$department = $this->upsertDepartment($organization, $branch, $key, $deptDef);
|
||||
$departmentsByCode[$deptDef['code']] = $department;
|
||||
$result['departments']++;
|
||||
}
|
||||
|
||||
$queuesByCode = [];
|
||||
if (! $skipStages) {
|
||||
$maxQueues = $this->plans->maxQueues($organization);
|
||||
$existingCount = ServiceQueue::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
|
||||
foreach ($package->stages() as $stageDef) {
|
||||
if (! $includeOptional && ($stageDef['optional'] ?? false)) {
|
||||
$result['skipped_stages'][] = $stageDef['code'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$already = ServiceQueue::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('settings->external_key', $this->stageExternalKey($key, $stageDef['code'], $branch->id))
|
||||
->exists();
|
||||
|
||||
if (! $already && $maxQueues !== null && $existingCount >= $maxQueues) {
|
||||
$result['skipped_stages'][] = $stageDef['code'];
|
||||
$result['skipped_reason'] = 'plan_queue_limit';
|
||||
continue;
|
||||
}
|
||||
|
||||
$department = $departmentsByCode[$stageDef['department']] ?? null;
|
||||
$queue = $this->upsertStage($organization, $branch, $department, $key, $stageDef);
|
||||
if (! $already) {
|
||||
$existingCount++;
|
||||
}
|
||||
$queuesByCode[$stageDef['code']] = $queue;
|
||||
$result['stages']++;
|
||||
|
||||
foreach ($stageDef['service_points'] ?? [] as $index => $pointDef) {
|
||||
$this->upsertServicePoint($organization, $branch, $department, $queue, $key, $stageDef['code'], $pointDef, $index);
|
||||
$result['service_points']++;
|
||||
}
|
||||
}
|
||||
|
||||
$workflow = $this->upsertWorkflow($organization, $branch, $package, $key, $queuesByCode);
|
||||
$result['workflow_id'] = $workflow?->id;
|
||||
}
|
||||
|
||||
$settings = $organization->settings ?? [];
|
||||
$settings['industry'] = $key;
|
||||
$settings['industry_package'] = [
|
||||
'key' => $key,
|
||||
'version' => (int) config('industry_packages.version', 1),
|
||||
'installed_at' => now()->toIso8601String(),
|
||||
'branch_id' => $branch->id,
|
||||
'stages_installed' => array_keys($queuesByCode),
|
||||
'skipped_stages' => $result['skipped_stages'],
|
||||
'skipped_reason' => $result['skipped_reason'],
|
||||
'ticket_entity' => $package->ticketEntity(),
|
||||
'terminology' => $package->terminology(),
|
||||
'announcement' => $package->announcement(),
|
||||
'display_layout' => $package->displayLayout(),
|
||||
'kpis' => $package->kpis(),
|
||||
];
|
||||
$organization->update(['settings' => $settings]);
|
||||
|
||||
AuditLogger::record(
|
||||
$organization->owner_ref,
|
||||
'organization.updated',
|
||||
$organization->id,
|
||||
$actor,
|
||||
Organization::class,
|
||||
$organization->id,
|
||||
['industry_package' => $key, 'install' => $result],
|
||||
);
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
protected function applyPackageSettings(Organization $organization, IndustryPackage $package): void
|
||||
{
|
||||
// Persisted fully after install; noop placeholder for future hooks.
|
||||
unset($organization, $package);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{code: string, name: string, type?: string} $deptDef
|
||||
*/
|
||||
protected function upsertDepartment(Organization $organization, Branch $branch, string $packageKey, array $deptDef): Department
|
||||
{
|
||||
$externalKey = $this->departmentExternalKey($packageKey, $deptDef['code'], $branch->id);
|
||||
|
||||
$department = Department::query()
|
||||
->where('branch_id', $branch->id)
|
||||
->where('external_key', $externalKey)
|
||||
->first();
|
||||
|
||||
if (! $department) {
|
||||
$department = Department::query()
|
||||
->where('branch_id', $branch->id)
|
||||
->where('name', $deptDef['name'])
|
||||
->whereNull('external_key')
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($department) {
|
||||
$department->update([
|
||||
'name' => $deptDef['name'],
|
||||
'type' => $deptDef['type'] ?? 'general',
|
||||
'external_key' => $externalKey,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
return $department->fresh();
|
||||
}
|
||||
|
||||
return Department::create([
|
||||
'owner_ref' => $organization->owner_ref,
|
||||
'branch_id' => $branch->id,
|
||||
'name' => $deptDef['name'],
|
||||
'type' => $deptDef['type'] ?? 'general',
|
||||
'external_key' => $externalKey,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $stageDef
|
||||
*/
|
||||
protected function upsertStage(
|
||||
Organization $organization,
|
||||
Branch $branch,
|
||||
?Department $department,
|
||||
string $packageKey,
|
||||
array $stageDef,
|
||||
): ServiceQueue {
|
||||
$externalKey = $this->stageExternalKey($packageKey, $stageDef['code'], $branch->id);
|
||||
|
||||
$queue = ServiceQueue::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('settings->external_key', $externalKey)
|
||||
->first();
|
||||
|
||||
$settings = [
|
||||
'external_key' => $externalKey,
|
||||
'routing_mode' => $stageDef['routing_mode'] ?? 'shared_pool',
|
||||
'routing_strategy' => $stageDef['routing_strategy'] ?? 'round_robin',
|
||||
'industry_package' => $packageKey,
|
||||
'stage_code' => $stageDef['code'],
|
||||
'optional' => (bool) ($stageDef['optional'] ?? false),
|
||||
];
|
||||
|
||||
if ($queue) {
|
||||
$queue->update([
|
||||
'department_id' => $department?->id,
|
||||
'name' => $stageDef['name'],
|
||||
'prefix' => $stageDef['prefix'],
|
||||
'strategy' => $stageDef['strategy'] ?? 'fifo',
|
||||
'settings' => array_merge($queue->settings ?? [], $settings),
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
return $queue->fresh();
|
||||
}
|
||||
|
||||
return ServiceQueue::create([
|
||||
'owner_ref' => $organization->owner_ref,
|
||||
'organization_id' => $organization->id,
|
||||
'branch_id' => $branch->id,
|
||||
'department_id' => $department?->id,
|
||||
'name' => $stageDef['name'],
|
||||
'prefix' => $stageDef['prefix'],
|
||||
'strategy' => $stageDef['strategy'] ?? 'fifo',
|
||||
'settings' => $settings,
|
||||
'is_active' => true,
|
||||
'is_paused' => false,
|
||||
'ticket_sequence' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{name: string, destination?: string, code?: string} $pointDef
|
||||
*/
|
||||
protected function upsertServicePoint(
|
||||
Organization $organization,
|
||||
Branch $branch,
|
||||
?Department $department,
|
||||
ServiceQueue $queue,
|
||||
string $packageKey,
|
||||
string $stageCode,
|
||||
array $pointDef,
|
||||
int $index,
|
||||
): Counter {
|
||||
$code = $pointDef['code'] ?? strtoupper(substr($stageCode, 0, 2)).($index + 1);
|
||||
$externalKey = $this->pointExternalKey($packageKey, $stageCode, $code, $branch->id);
|
||||
|
||||
$counter = Counter::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('settings->external_key', $externalKey)
|
||||
->first();
|
||||
|
||||
$attributes = [
|
||||
'department_id' => $department?->id,
|
||||
'name' => $pointDef['name'],
|
||||
'code' => $code,
|
||||
'destination' => $pointDef['destination'] ?? $pointDef['name'],
|
||||
'status' => 'available',
|
||||
'settings' => [
|
||||
'external_key' => $externalKey,
|
||||
'industry_package' => $packageKey,
|
||||
'stage_code' => $stageCode,
|
||||
],
|
||||
'is_active' => true,
|
||||
];
|
||||
|
||||
if ($counter) {
|
||||
$counter->update(array_merge($attributes, [
|
||||
'settings' => array_merge($counter->settings ?? [], $attributes['settings']),
|
||||
]));
|
||||
$counter = $counter->fresh();
|
||||
} else {
|
||||
$counter = Counter::create(array_merge($attributes, [
|
||||
'owner_ref' => $organization->owner_ref,
|
||||
'organization_id' => $organization->id,
|
||||
'branch_id' => $branch->id,
|
||||
]));
|
||||
}
|
||||
|
||||
if (! $queue->counters()->where('queue_counters.id', $counter->id)->exists()) {
|
||||
$queue->counters()->attach($counter->id, ['priority' => $index]);
|
||||
}
|
||||
|
||||
return $counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ServiceQueue> $queuesByCode
|
||||
*/
|
||||
protected function upsertWorkflow(
|
||||
Organization $organization,
|
||||
Branch $branch,
|
||||
IndustryPackage $package,
|
||||
string $packageKey,
|
||||
array $queuesByCode,
|
||||
): ?Workflow {
|
||||
if ($queuesByCode === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$meta = $package->workflowMeta();
|
||||
$externalKey = "industry:{$packageKey}:workflow:{$branch->id}";
|
||||
|
||||
$workflow = Workflow::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('settings->external_key', $externalKey)
|
||||
->first();
|
||||
|
||||
if (! $workflow) {
|
||||
$workflow = Workflow::create([
|
||||
'owner_ref' => $organization->owner_ref,
|
||||
'organization_id' => $organization->id,
|
||||
'branch_id' => $branch->id,
|
||||
'name' => $meta['name'] ?? $package->label().' workflow',
|
||||
'description' => $meta['description'] ?? null,
|
||||
'industry_template' => $packageKey,
|
||||
'is_active' => true,
|
||||
'settings' => ['external_key' => $externalKey],
|
||||
]);
|
||||
} else {
|
||||
$workflow->update([
|
||||
'name' => $meta['name'] ?? $workflow->name,
|
||||
'description' => $meta['description'] ?? $workflow->description,
|
||||
'industry_template' => $packageKey,
|
||||
'is_active' => true,
|
||||
'settings' => array_merge($workflow->settings ?? [], ['external_key' => $externalKey]),
|
||||
]);
|
||||
}
|
||||
|
||||
$sort = 1;
|
||||
foreach ($package->stages() as $stageDef) {
|
||||
$queue = $queuesByCode[$stageDef['code']] ?? null;
|
||||
if (! $queue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$step = WorkflowStep::query()
|
||||
->where('workflow_id', $workflow->id)
|
||||
->where('service_queue_id', $queue->id)
|
||||
->first();
|
||||
|
||||
if ($step) {
|
||||
$step->update([
|
||||
'name' => $stageDef['name'],
|
||||
'sort_order' => $sort,
|
||||
'settings' => ['stage_code' => $stageDef['code']],
|
||||
]);
|
||||
} else {
|
||||
WorkflowStep::create([
|
||||
'workflow_id' => $workflow->id,
|
||||
'service_queue_id' => $queue->id,
|
||||
'name' => $stageDef['name'],
|
||||
'sort_order' => $sort,
|
||||
'settings' => ['stage_code' => $stageDef['code']],
|
||||
]);
|
||||
}
|
||||
$sort++;
|
||||
}
|
||||
|
||||
return $workflow->fresh(['steps']);
|
||||
}
|
||||
|
||||
public function departmentExternalKey(string $packageKey, string $code, int $branchId): string
|
||||
{
|
||||
return "industry:{$packageKey}:dept:{$code}:{$branchId}";
|
||||
}
|
||||
|
||||
public function stageExternalKey(string $packageKey, string $code, int $branchId): string
|
||||
{
|
||||
return "industry:{$packageKey}:stage:{$code}:{$branchId}";
|
||||
}
|
||||
|
||||
public function pointExternalKey(string $packageKey, string $stageCode, string $pointCode, int $branchId): string
|
||||
{
|
||||
return "industry:{$packageKey}:point:{$stageCode}:{$pointCode}:{$branchId}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Qms\Industry;
|
||||
|
||||
class IndustryPackageRegistry
|
||||
{
|
||||
/**
|
||||
* @return array<string, string> key => label
|
||||
*/
|
||||
public function labels(): array
|
||||
{
|
||||
$labels = [];
|
||||
foreach (array_keys(config('industry_packages.packages', [])) as $key) {
|
||||
$package = $this->get($key);
|
||||
if ($package) {
|
||||
$labels[$key] = $package->label();
|
||||
}
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
public function get(string $key): ?IndustryPackage
|
||||
{
|
||||
$definition = config("industry_packages.packages.{$key}");
|
||||
if (! is_array($definition)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IndustryPackage($key, $definition);
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return $this->get($key) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<IndustryPackage>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$packages = [];
|
||||
foreach (array_keys(config('industry_packages.packages', [])) as $key) {
|
||||
$package = $this->get($key);
|
||||
if ($package) {
|
||||
$packages[] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
public function resolveKey(?string $key): string
|
||||
{
|
||||
$key = $key ?: 'custom';
|
||||
|
||||
return $this->has($key) ? $key : 'custom';
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,11 @@
|
||||
namespace App\Services\Qms;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\Department;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Services\Qms\Industry\IndustryPackageInstaller;
|
||||
use App\Services\Qms\Industry\IndustryPackageRegistry;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class OrganizationResolver
|
||||
@@ -69,6 +70,8 @@ class OrganizationResolver
|
||||
public function completeOnboarding(User $user, array $data): Organization
|
||||
{
|
||||
$ref = $user->ownerRef();
|
||||
$registry = app(IndustryPackageRegistry::class);
|
||||
$industry = $registry->resolveKey($data['industry'] ?? 'custom');
|
||||
|
||||
$organization = Organization::create([
|
||||
'owner_ref' => $ref,
|
||||
@@ -77,7 +80,7 @@ class OrganizationResolver
|
||||
'timezone' => $data['timezone'] ?? config('app.timezone', 'UTC'),
|
||||
'settings' => [
|
||||
'onboarded' => true,
|
||||
'industry' => $data['industry'] ?? 'custom',
|
||||
'industry' => $industry,
|
||||
'appointment_mode' => $data['appointment_mode'] ?? 'hybrid',
|
||||
],
|
||||
]);
|
||||
@@ -93,18 +96,15 @@ class OrganizationResolver
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
Department::create([
|
||||
'owner_ref' => $ref,
|
||||
'branch_id' => $branch->id,
|
||||
'name' => 'General Reception',
|
||||
'type' => 'reception',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
AuditLogger::record($ref, 'organization.created', $organization->id, $ref, Organization::class, $organization->id);
|
||||
AuditLogger::record($ref, 'branch.created', $organization->id, $ref, Branch::class, $branch->id);
|
||||
|
||||
return $organization;
|
||||
app(IndustryPackageInstaller::class)->install($organization->fresh(), $branch, $industry, [
|
||||
'actor_ref' => $ref,
|
||||
'include_optional' => true,
|
||||
]);
|
||||
|
||||
return $organization->fresh();
|
||||
}
|
||||
|
||||
public function branchScope(?Member $member): ?int
|
||||
|
||||
@@ -11,6 +11,7 @@ class VoiceAnnouncementService
|
||||
public function announceCalled(Ticket $ticket, Counter $counter, ?string $locale = null): VoiceAnnouncement
|
||||
{
|
||||
$locale ??= data_get($ticket->serviceQueue?->settings, 'announcement_locale', 'en');
|
||||
$ticket->loadMissing(['organization', 'serviceQueue']);
|
||||
$message = $this->buildMessage($ticket, $counter, $locale);
|
||||
|
||||
return VoiceAnnouncement::create([
|
||||
@@ -29,27 +30,37 @@ class VoiceAnnouncementService
|
||||
|
||||
protected function buildMessage(Ticket $ticket, Counter $counter, string $locale): string
|
||||
{
|
||||
$templates = config('qms.announcement_templates');
|
||||
$template = $templates[$locale] ?? $templates['en'];
|
||||
$destination = $counter->displayDestination();
|
||||
$staff = trim((string) ($counter->staff_display_name ?? ''));
|
||||
$queueName = trim((string) ($ticket->serviceQueue?->name ?? ''));
|
||||
|
||||
// Prefer rich healthcare-style copy when staff is known:
|
||||
// "A005, Dr. Mensah, Consultation Room 4."
|
||||
if ($staff !== '') {
|
||||
$parts = array_values(array_filter([
|
||||
$ticket->ticket_number,
|
||||
$staff,
|
||||
$queueName !== '' ? $queueName.' '.$destination : $destination,
|
||||
]));
|
||||
$industryAnnouncement = (array) data_get($ticket->organization?->settings, 'industry_package.announcement', []);
|
||||
$global = config('qms.announcement_templates', []);
|
||||
|
||||
return implode(', ', $parts).'.';
|
||||
if ($staff !== '') {
|
||||
$richDestination = $queueName !== '' && ! str_contains(strtolower($destination), strtolower($queueName))
|
||||
? trim($queueName.' '.$destination)
|
||||
: $destination;
|
||||
$template = $industryAnnouncement['with_staff']
|
||||
?? $global['with_staff']
|
||||
?? ':ticket, :staff, :destination.';
|
||||
|
||||
return rtrim(str_replace(
|
||||
[':ticket', ':staff', ':destination', ':counter'],
|
||||
[$ticket->ticket_number, $staff, $richDestination, $richDestination],
|
||||
$template,
|
||||
), '.').'.';
|
||||
}
|
||||
|
||||
$template = $industryAnnouncement[$locale]
|
||||
?? $industryAnnouncement['en']
|
||||
?? $global[$locale]
|
||||
?? $global['en']
|
||||
?? 'Ticket :ticket, please proceed to :destination.';
|
||||
|
||||
return str_replace(
|
||||
[':ticket', ':counter'],
|
||||
[$ticket->ticket_number, $destination],
|
||||
[':ticket', ':destination', ':counter'],
|
||||
[$ticket->ticket_number, $destination, $destination],
|
||||
$template,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user