Deploy Ladill POS / deploy (push) Successful in 42s
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.
149 lines
4.4 KiB
PHP
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();
|
|
}
|
|
}
|