Files
ladill-pos/app/Http/Controllers/Pos/DeviceController.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

173 lines
5.7 KiB
PHP

<?php
namespace App\Http\Controllers\Pos;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Pos\Concerns\ScopesToAccount;
use App\Models\PosDevice;
use App\Models\PosLocation;
use App\Services\Pos\DeviceService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
class DeviceController extends Controller
{
use ScopesToAccount;
public function __construct(private DeviceService $devices) {}
public function index(Request $request): View
{
$owner = $this->ownerRef($request);
$query = PosDevice::owned($owner)->with('location')->orderBy('name');
$this->scopeToLocation($request, $query);
$devices = $query->paginate(25);
$statsQuery = PosDevice::owned($owner);
$this->scopeToLocation($request, $statsQuery);
$heroStats = [
'total' => (clone $statsQuery)->count(),
'online' => (clone $statsQuery)
->where('status', PosDevice::STATUS_ONLINE)
->where('last_online_at', '>=', now()->subMinutes(10))
->count(),
'displays' => (clone $statsQuery)
->whereIn('type', ['customer_display', 'kitchen_display'])
->count(),
];
return view('pos.devices.index', [
'devices' => $devices,
'deviceTypes' => config('pos.device_types', []),
'heroStats' => $heroStats,
]);
}
public function create(Request $request): View
{
return view('pos.devices.create', [
'deviceTypes' => config('pos.device_types', []),
'hints' => config('pos.device_registration_hints', []),
'locations' => $this->locations($request),
'defaultLocationId' => $this->location($request)->id,
]);
}
public function store(Request $request): RedirectResponse
{
$types = array_keys(config('pos.device_types', []));
$owner = $this->ownerRef($request);
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'type' => ['required', 'string', Rule::in($types)],
'location_id' => ['nullable', 'integer'],
]);
$locationId = $validated['location_id'] ?? null;
if ($locationId) {
$this->assertLocationOwned($request, (int) $locationId);
}
if (in_array($validated['type'], ['customer_display', 'kitchen_display'], true) && ! $locationId) {
return back()->withInput()->with('error', 'Choose a branch for this display device.');
}
$this->devices->register([
'owner_ref' => $owner,
'name' => $validated['name'],
'type' => $validated['type'],
'location_id' => $locationId,
]);
return redirect()->route('pos.devices.index')->with('success', 'Device registered.');
}
public function edit(Request $request, PosDevice $device): View
{
$this->authorizeOwner($request, $device);
return view('pos.devices.edit', [
'device' => $device,
'deviceTypes' => config('pos.device_types', []),
'hints' => config('pos.device_registration_hints', []),
'locations' => $this->locations($request),
'openUrl' => $this->devices->openUrl($device),
]);
}
public function update(Request $request, PosDevice $device): RedirectResponse
{
$this->authorizeOwner($request, $device);
$types = array_keys(config('pos.device_types', []));
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'type' => ['required', 'string', Rule::in($types)],
'location_id' => ['nullable', 'integer'],
'status' => ['nullable', 'string', Rule::in([
PosDevice::STATUS_ONLINE,
PosDevice::STATUS_OFFLINE,
PosDevice::STATUS_MAINTENANCE,
])],
]);
if (! empty($validated['location_id'])) {
$this->assertLocationOwned($request, (int) $validated['location_id']);
}
$device->update([
'name' => $validated['name'],
'type' => $validated['type'],
'location_id' => $validated['location_id'] ?: null,
'status' => $validated['status'] ?? $device->status,
]);
return redirect()->route('pos.devices.index')->with('success', 'Device updated.');
}
public function destroy(Request $request, PosDevice $device): RedirectResponse
{
$this->authorizeOwner($request, $device);
$device->delete();
return redirect()->route('pos.devices.index')->with('success', 'Device removed.');
}
public function regenerateToken(Request $request, PosDevice $device): RedirectResponse
{
$this->authorizeOwner($request, $device);
$this->devices->regenerateToken($device);
return back()->with('success', 'Device token regenerated. Re-open the device URL if needed.');
}
/** @return \Illuminate\Support\Collection<int, PosLocation> */
protected function locations(Request $request)
{
$query = PosLocation::owned($this->ownerRef($request))->orderBy('name');
$scope = $this->locationScope($request);
if ($scope !== null) {
$query->whereKey($scope);
}
return $query->get();
}
protected function assertLocationOwned(Request $request, int $locationId): void
{
$exists = PosLocation::owned($this->ownerRef($request))->whereKey($locationId)->exists();
abort_unless($exists, 404);
$scope = $this->locationScope($request);
if ($scope !== null) {
abort_unless($locationId === $scope, 404);
}
}
}