Files
ladill-pos/app/Services/Pos/DeviceService.php
T
isaacclad c01518b0ee
Deploy Ladill POS / deploy (push) Successful in 42s
Add Frontdesk-style Devices registry for POS hardware.
Register tills, customer displays, kitchen screens, printers, scanners,
and tablets per branch. Customer displays share the branch display token
and mark online when the public screen polls; stale devices go offline
on a schedule.
2026-07-15 21:49:21 +00:00

149 lines
4.4 KiB
PHP

<?php
namespace App\Services\Pos;
use App\Models\PosCustomerDisplay;
use App\Models\PosDevice;
use App\Models\PosLocation;
use Illuminate\Support\Str;
class DeviceService
{
public function __construct(private CustomerDisplayService $displays) {}
public function generateToken(): string
{
return Str::random(48);
}
public function findByToken(string $token): ?PosDevice
{
return PosDevice::query()->where('device_token', $token)->first();
}
public function recordHeartbeat(PosDevice $device): PosDevice
{
$device->update([
'status' => PosDevice::STATUS_ONLINE,
'last_online_at' => now(),
]);
return $device->fresh();
}
/** Mark token-bearing devices online when their public endpoints are hit. */
public function touchByToken(?string $token): void
{
if (! $token) {
return;
}
$device = $this->findByToken($token);
if ($device) {
$this->recordHeartbeat($device);
}
}
public function markStaleDevicesOffline(int $minutes = 10): int
{
return PosDevice::query()
->where('status', PosDevice::STATUS_ONLINE)
->where(function ($q) use ($minutes) {
$q->whereNull('last_online_at')
->orWhere('last_online_at', '<', now()->subMinutes($minutes));
})
->update(['status' => PosDevice::STATUS_OFFLINE]);
}
/** @return array<string, mixed> */
public function defaultConfigForType(string $type): array
{
return match ($type) {
'customer_display' => ['role' => 'customer_facing'],
'kitchen_display' => ['role' => 'kitchen'],
'receipt_printer' => ['paper_mm' => 80, 'driver' => 'browser'],
'register' => ['role' => 'till'],
default => [],
};
}
/**
* Create a device; customer_display types share the branch display token.
*
* @param array{name: string, type: string, location_id?: ?int, owner_ref: string} $data
*/
public function register(array $data): PosDevice
{
$type = $data['type'];
$locationId = isset($data['location_id']) ? (int) $data['location_id'] : null;
$token = null;
if (in_array($type, config('pos.device_token_types', []), true)) {
$token = $this->tokenForNewDevice($type, $locationId, $data['owner_ref']);
}
return PosDevice::create([
'owner_ref' => $data['owner_ref'],
'location_id' => $locationId ?: null,
'name' => $data['name'],
'type' => $type,
'status' => PosDevice::STATUS_OFFLINE,
'device_token' => $token,
'config' => $this->defaultConfigForType($type),
]);
}
public function regenerateToken(PosDevice $device): PosDevice
{
if (! $device->usesToken()) {
return $device;
}
$token = $this->generateToken();
if ($device->type === 'customer_display' && $device->location_id) {
$location = PosLocation::query()->find($device->location_id);
if ($location) {
$display = $this->displays->ensureForLocation($location);
$display->update(['token' => $token]);
}
}
$device->update(['device_token' => $token]);
return $device->fresh();
}
public function openUrl(PosDevice $device): ?string
{
if (! $device->device_token) {
return null;
}
return match ($device->type) {
'customer_display' => route('pos.customer-display.show', ['token' => $device->device_token]),
'kitchen_display' => route('pos.kitchen'),
default => null,
};
}
private function tokenForNewDevice(string $type, ?int $locationId, string $ownerRef): string
{
if ($type === 'customer_display' && $locationId) {
$location = PosLocation::query()
->where('owner_ref', $ownerRef)
->whereKey($locationId)
->first();
if ($location) {
$display = $this->displays->ensureForLocation($location);
// Keep a single active display token per branch; reuse it for the device.
return $display->token;
}
}
return $this->generateToken();
}
}