Files
isaacclad ede4aaa10c
Deploy Ladill POS / deploy (push) Successful in 46s
Add public token-gated kitchen display for POS devices.
Kitchen display devices open a full-screen KDS without staff login,
with feed/stream/bump over the device token. Shared kitchen board
logic is extracted so the signed-in Kitchen page stays in sync.
2026-07-15 22:52:28 +00:00

243 lines
7.9 KiB
PHP

<?php
namespace Tests\Feature;
use App\Http\Middleware\EnsurePlatformSession;
use App\Models\PosCustomerDisplay;
use App\Models\PosDevice;
use App\Models\PosLocation;
use App\Models\User;
use App\Services\Pos\DeviceService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PosDeviceTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->withoutMiddleware(EnsurePlatformSession::class);
$this->withoutVite();
}
private function user(): User
{
return User::create([
'public_id' => 'u-'.uniqid(),
'name' => 'Owner',
'email' => uniqid().'@example.com',
]);
}
private function location(User $user): PosLocation
{
return PosLocation::create([
'owner_ref' => $user->public_id,
'name' => 'Main register',
'currency' => 'GHS',
]);
}
public function test_can_register_register_device_without_token(): void
{
$user = $this->user();
$location = $this->location($user);
$this->actingAs($user)
->post(route('pos.devices.store'), [
'name' => 'Front till',
'type' => 'register',
'location_id' => $location->id,
])
->assertRedirect(route('pos.devices.index'));
$device = PosDevice::query()->where('owner_ref', $user->public_id)->first();
$this->assertNotNull($device);
$this->assertSame('register', $device->type);
$this->assertNull($device->device_token);
}
public function test_customer_display_device_reuses_branch_display_token(): void
{
$user = $this->user();
$location = $this->location($user);
$this->actingAs($user)
->post(route('pos.devices.store'), [
'name' => 'Counter screen',
'type' => 'customer_display',
'location_id' => $location->id,
])
->assertRedirect(route('pos.devices.index'));
$device = PosDevice::query()->where('owner_ref', $user->public_id)->firstOrFail();
$display = PosCustomerDisplay::query()->where('location_id', $location->id)->firstOrFail();
$this->assertNotEmpty($device->device_token);
$this->assertSame($display->token, $device->device_token);
}
public function test_customer_display_device_requires_branch(): void
{
$user = $this->user();
$this->location($user);
$this->actingAs($user)
->from(route('pos.devices.create'))
->post(route('pos.devices.store'), [
'name' => 'Orphan screen',
'type' => 'customer_display',
'location_id' => '',
])
->assertRedirect(route('pos.devices.create'))
->assertSessionHas('error');
}
public function test_devices_index_lists_owned_devices(): void
{
$user = $this->user();
$location = $this->location($user);
PosDevice::create([
'owner_ref' => $user->public_id,
'location_id' => $location->id,
'name' => 'Kitchen pad',
'type' => 'tablet',
'status' => 'offline',
]);
$this->actingAs($user)
->get(route('pos.devices.index'))
->assertOk()
->assertSee('Kitchen pad')
->assertSee('Devices');
}
public function test_regenerate_token_updates_customer_display(): void
{
$user = $this->user();
$location = $this->location($user);
$device = app(DeviceService::class)->register([
'owner_ref' => $user->public_id,
'name' => 'Display',
'type' => 'customer_display',
'location_id' => $location->id,
]);
$old = $device->device_token;
$this->actingAs($user)
->post(route('pos.devices.regenerate-token', $device))
->assertRedirect();
$device->refresh();
$display = PosCustomerDisplay::query()->where('location_id', $location->id)->firstOrFail();
$this->assertNotSame($old, $device->device_token);
$this->assertSame($device->device_token, $display->token);
}
public function test_customer_display_poll_marks_device_online(): void
{
$user = $this->user();
$location = $this->location($user);
$device = app(DeviceService::class)->register([
'owner_ref' => $user->public_id,
'name' => 'Display',
'type' => 'customer_display',
'location_id' => $location->id,
]);
$this->getJson(route('pos.customer-display.state', ['token' => $device->device_token]))
->assertOk();
$device->refresh();
$this->assertSame('online', $device->status);
$this->assertNotNull($device->last_online_at);
}
public function test_kitchen_display_device_opens_public_url_without_auth(): void
{
$user = $this->user();
$location = $this->location($user);
$device = app(DeviceService::class)->register([
'owner_ref' => $user->public_id,
'name' => 'Pass tablet',
'type' => 'kitchen_display',
'location_id' => $location->id,
]);
$this->assertNotEmpty($device->device_token);
$this->get(route('pos.kitchen-display.show', ['token' => $device->device_token]))
->assertOk()
->assertSee('Kitchen display')
->assertSee('Main register');
$this->getJson(route('pos.kitchen-display.feed', ['token' => $device->device_token]))
->assertOk()
->assertJsonStructure(['tickets', 'server_time']);
$device->refresh();
$this->assertSame('online', $device->status);
}
public function test_kitchen_display_bump_advances_line_state(): void
{
$user = $this->user();
$location = $this->location($user);
$device = app(DeviceService::class)->register([
'owner_ref' => $user->public_id,
'name' => 'KDS',
'type' => 'kitchen_display',
'location_id' => $location->id,
]);
$sale = \App\Models\PosSale::create([
'owner_ref' => $user->public_id,
'location_id' => $location->id,
'reference' => 'POS-KDS-1',
'currency' => 'GHS',
'status' => 'pending',
'payment_method' => 'cash',
'order_type' => 'dine_in',
'kitchen_status' => \App\Models\PosSale::KITCHEN_ACTIVE,
'kitchen_sent_at' => now(),
'total_minor' => 1000,
'subtotal_minor' => 1000,
]);
$line = \App\Models\PosSaleLine::create([
'pos_sale_id' => $sale->id,
'name' => 'Jollof',
'unit_price_minor' => 1000,
'quantity' => 1,
'line_total_minor' => 1000,
'position' => 0,
'kitchen_state' => \App\Models\PosSaleLine::KITCHEN_QUEUED,
]);
$this->postJson(route('pos.kitchen-display.bump', [
'token' => $device->device_token,
'line' => $line->id,
]))->assertOk()->assertJsonPath('state', 'preparing');
$this->assertSame('preparing', $line->fresh()->kitchen_state);
}
public function test_device_service_open_url_for_kitchen_is_public(): void
{
$user = $this->user();
$location = $this->location($user);
$device = app(DeviceService::class)->register([
'owner_ref' => $user->public_id,
'name' => 'KDS',
'type' => 'kitchen_display',
'location_id' => $location->id,
]);
$url = app(DeviceService::class)->openUrl($device);
$this->assertNotNull($url);
$this->assertStringContainsString('/kitchen-display/', $url);
$this->assertStringContainsString($device->device_token, $url);
}
}