Deploy Ladill Queue / deploy (push) Successful in 56s
Phases 1–6: tickets, counters, displays, appointments, workflows, rules, analytics, reports, feedback, admin, device API, and Gitea deploy workflow for queue.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
193 lines
6.6 KiB
PHP
193 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Branch;
|
|
use App\Models\Organization;
|
|
use App\Models\ServiceQueue;
|
|
use App\Models\User;
|
|
use App\Services\Qms\OrganizationResolver;
|
|
use App\Services\Qms\QueueEngine;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class QmsQueueTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUpOrg(): array
|
|
{
|
|
$user = User::create([
|
|
'public_id' => 'test-owner-uuid',
|
|
'name' => 'Test User',
|
|
'email' => 'test@example.com',
|
|
'password' => bcrypt('password'),
|
|
]);
|
|
$resolver = app(OrganizationResolver::class);
|
|
$org = $resolver->completeOnboarding($user, [
|
|
'organization_name' => 'Test Org',
|
|
'industry' => 'retail',
|
|
'appointment_mode' => 'hybrid',
|
|
'branch_name' => 'Main',
|
|
'timezone' => 'UTC',
|
|
]);
|
|
$branch = Branch::first();
|
|
$queue = ServiceQueue::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $org->id,
|
|
'branch_id' => $branch->id,
|
|
'name' => 'Reception',
|
|
'prefix' => 'A',
|
|
'strategy' => 'fifo',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return [$user, $org, $branch, $queue];
|
|
}
|
|
|
|
public function test_issue_ticket_increments_sequence(): void
|
|
{
|
|
[$user, , , $queue] = $this->setUpOrg();
|
|
$engine = app(QueueEngine::class);
|
|
|
|
$ticket = $engine->issueTicket($queue, $user->public_id, ['source' => 'api']);
|
|
$this->assertSame('A001', $ticket->ticket_number);
|
|
$this->assertSame('waiting', $ticket->status);
|
|
|
|
$ticket2 = $engine->issueTicket($queue->fresh(), $user->public_id);
|
|
$this->assertSame('A002', $ticket2->ticket_number);
|
|
}
|
|
|
|
public function test_health_endpoint(): void
|
|
{
|
|
$this->getJson('/api/health')->assertOk()->assertJson(['app' => 'queue']);
|
|
}
|
|
|
|
public function test_appointment_check_in_issues_ticket(): void
|
|
{
|
|
[$user, $org, $branch, $queue] = $this->setUpOrg();
|
|
|
|
$appointment = \App\Models\QueueAppointment::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $org->id,
|
|
'branch_id' => $branch->id,
|
|
'service_queue_id' => $queue->id,
|
|
'customer_name' => 'Jane Doe',
|
|
'status' => 'scheduled',
|
|
'scheduled_at' => now()->addHour(),
|
|
'reference' => 'APT-TEST',
|
|
]);
|
|
|
|
$ticket = app(\App\Services\Qms\AppointmentService::class)->checkIn($appointment, $user->public_id);
|
|
|
|
$this->assertSame('appointment', $ticket->priority);
|
|
$this->assertSame('checked_in', $appointment->fresh()->status);
|
|
$this->assertSame($ticket->id, $appointment->fresh()->ticket_id);
|
|
}
|
|
|
|
public function test_workflow_metadata_attached_on_issue(): void
|
|
{
|
|
[$user, $org, $branch, $queue] = $this->setUpOrg();
|
|
|
|
$workflow = \App\Models\Workflow::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $org->id,
|
|
'name' => 'Test flow',
|
|
'is_active' => true,
|
|
]);
|
|
\App\Models\WorkflowStep::create([
|
|
'workflow_id' => $workflow->id,
|
|
'service_queue_id' => $queue->id,
|
|
'name' => 'Step 1',
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
$ticket = app(QueueEngine::class)->issueTicket($queue, $user->public_id);
|
|
$this->assertSame($workflow->id, data_get($ticket->metadata, 'workflow_id'));
|
|
}
|
|
|
|
public function test_routing_rule_redirects_ticket(): void
|
|
{
|
|
[$user, $org, $branch, $queue] = $this->setUpOrg();
|
|
$target = ServiceQueue::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $org->id,
|
|
'branch_id' => $branch->id,
|
|
'name' => 'VIP',
|
|
'prefix' => 'V',
|
|
'strategy' => 'fifo',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
\App\Models\QueueRule::create([
|
|
'owner_ref' => $user->public_id,
|
|
'service_queue_id' => $queue->id,
|
|
'rule_type' => 'routing',
|
|
'conditions' => ['source' => 'api'],
|
|
'actions' => ['target_queue_id' => $target->id],
|
|
'sort_order' => 1,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$ticket = app(QueueEngine::class)->issueTicket($queue, $user->public_id, ['source' => 'api']);
|
|
$this->assertSame($target->id, $ticket->service_queue_id);
|
|
}
|
|
|
|
public function test_service_session_tracked(): void
|
|
{
|
|
[$user, , , $queue] = $this->setUpOrg();
|
|
$engine = app(QueueEngine::class);
|
|
$ticket = $engine->issueTicket($queue, $user->public_id);
|
|
$counter = \App\Models\Counter::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $queue->organization_id,
|
|
'branch_id' => $queue->branch_id,
|
|
'name' => 'C1',
|
|
'status' => 'available',
|
|
'is_active' => true,
|
|
]);
|
|
$engine->callTicket($ticket, $counter, $user->public_id);
|
|
$engine->startServing($ticket->fresh(), $user->public_id);
|
|
$engine->completeTicket($ticket->fresh(), $user->public_id);
|
|
|
|
$this->assertDatabaseHas('queue_service_sessions', [
|
|
'ticket_id' => $ticket->id,
|
|
]);
|
|
$session = \App\Models\ServiceSession::where('ticket_id', $ticket->id)->first();
|
|
$this->assertNotNull($session->ended_at);
|
|
}
|
|
|
|
public function test_reports_sqlite_compatible(): void
|
|
{
|
|
[$user, $org, , $queue] = $this->setUpOrg();
|
|
app(QueueEngine::class)->issueTicket($queue, $user->public_id, ['source' => 'api']);
|
|
|
|
$reports = app(\App\Services\Qms\ReportService::class);
|
|
$from = now()->subDay();
|
|
$to = now()->addDay();
|
|
$data = $reports->ticketsReport($user->public_id, $org->id, $from, $to);
|
|
$this->assertArrayHasKey('total_issued', $data);
|
|
$this->assertGreaterThanOrEqual(1, $data['total_issued']);
|
|
}
|
|
|
|
public function test_device_heartbeat(): void
|
|
{
|
|
[$user, $org, $branch] = $this->setUpOrg();
|
|
|
|
$device = \App\Models\Device::create([
|
|
'owner_ref' => $user->public_id,
|
|
'organization_id' => $org->id,
|
|
'branch_id' => $branch->id,
|
|
'name' => 'Kiosk 1',
|
|
'type' => 'kiosk',
|
|
'device_token' => 'test-device-token-123',
|
|
]);
|
|
|
|
$this->postJson('/api/devices/heartbeat', [], [
|
|
'Authorization' => 'Bearer test-device-token-123',
|
|
])->assertOk()->assertJsonPath('status', 'ok');
|
|
|
|
$this->assertSame('online', $device->fresh()->status);
|
|
}
|
|
}
|