Add service-point routing so call-next cannot steal assigned tickets.
Deploy Ladill Queue / deploy (push) Successful in 2m17s

Counters gain destination/staff metadata; tickets can be pre-assigned to a
service point with assigned_only queues for healthcare while shared_pool
preserves generic bank/government behavior. Display and announcements expose
ticket, staff, and destination clearly for Care and public boards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-17 17:21:56 +00:00
co-authored by Cursor
parent 2d1f8c1eff
commit 0854b431bc
17 changed files with 753 additions and 42 deletions
+10 -1
View File
@@ -19,7 +19,8 @@ class Counter extends Model
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'branch_id', 'department_id',
'name', 'code', 'status', 'settings', 'is_active',
'name', 'code', 'destination', 'staff_ref', 'staff_display_name',
'status', 'settings', 'is_active',
];
protected function casts(): array
@@ -30,6 +31,14 @@ class Counter extends Model
];
}
/** Public display label (Room 4, Counter 2, etc.). Falls back to name. */
public function displayDestination(): string
{
$destination = trim((string) ($this->destination ?? ''));
return $destination !== '' ? $destination : (string) $this->name;
}
protected static function booted(): void
{
static::creating(function (self $counter) {
+11 -1
View File
@@ -14,7 +14,7 @@ class Department extends Model
protected $table = 'queue_departments';
protected $fillable = [
'owner_ref', 'branch_id', 'name', 'type', 'is_active',
'owner_ref', 'branch_id', 'name', 'type', 'external_key', 'is_active',
];
protected function casts(): array
@@ -26,4 +26,14 @@ class Department extends Model
{
return $this->belongsTo(Branch::class, 'branch_id');
}
public function counters(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Counter::class, 'department_id');
}
public function serviceQueues(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(ServiceQueue::class, 'department_id');
}
}
+16
View File
@@ -67,4 +67,20 @@ class ServiceQueue extends Model
return $this->belongsToMany(Counter::class, 'queue_counter_service_queue', 'service_queue_id', 'counter_id')
->withPivot('priority');
}
/**
* shared_pool any linked service point may pull unassigned waiting tickets.
* assigned_only call-next only returns tickets pre-assigned to that service point.
*/
public function routingMode(): string
{
$mode = (string) data_get($this->settings, 'routing_mode', 'shared_pool');
return in_array($mode, ['shared_pool', 'assigned_only'], true) ? $mode : 'shared_pool';
}
public function usesAssignedOnlyRouting(): bool
{
return $this->routingMode() === 'assigned_only';
}
}
+7
View File
@@ -18,6 +18,7 @@ class Ticket extends Model
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'branch_id', 'service_queue_id', 'counter_id',
'assigned_counter_id',
'ticket_number', 'status', 'priority', 'position', 'customer_name', 'customer_phone',
'customer_email', 'source', 'qr_token', 'barcode', 'estimated_wait_seconds',
'issued_at', 'called_at', 'serving_started_at', 'completed_at', 'metadata',
@@ -62,6 +63,12 @@ class Ticket extends Model
return $this->belongsTo(Counter::class, 'counter_id');
}
/** Service point this ticket is routed to (may differ from serving counter until called). */
public function assignedCounter(): BelongsTo
{
return $this->belongsTo(Counter::class, 'assigned_counter_id');
}
public function events(): HasMany
{
return $this->hasMany(TicketEvent::class, 'ticket_id');