Deploy Ladill POS / deploy (push) Successful in 46s
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.
93 lines
2.9 KiB
PHP
93 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Pos;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Controllers\Pos\Concerns\ScopesToAccount;
|
|
use App\Models\PosSaleLine;
|
|
use App\Services\Pos\KitchenBoardService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class KitchenController extends Controller
|
|
{
|
|
use ScopesToAccount;
|
|
|
|
public function __construct(private KitchenBoardService $board) {}
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
return view('pos.kitchen.index', [
|
|
'stations' => $this->board->stations($this->ownerRef($request)),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Active kitchen tickets — open sales with fired-but-not-served lines.
|
|
* Used for the initial load and as a fallback when SSE is unavailable.
|
|
*/
|
|
public function feed(Request $request): JsonResponse
|
|
{
|
|
return response()->json($this->board->payload($this->ownerRef($request)));
|
|
}
|
|
|
|
/**
|
|
* Realtime feed over Server-Sent Events. Pushes the board whenever it
|
|
* changes (~1s tick), with a heartbeat. Bounded lifetime so PHP-FPM workers
|
|
* recycle — the browser's EventSource reconnects automatically.
|
|
*/
|
|
public function stream(Request $request): StreamedResponse
|
|
{
|
|
$owner = $this->ownerRef($request);
|
|
// Release the session lock early (DB driver doesn't lock, but be safe).
|
|
$request->session()->save();
|
|
|
|
$response = new StreamedResponse(function () use ($owner) {
|
|
while (ob_get_level() > 0) {
|
|
@ob_end_flush();
|
|
}
|
|
@set_time_limit(0);
|
|
|
|
$start = time();
|
|
$lastHash = null;
|
|
|
|
while (true) {
|
|
$payload = $this->board->payload($owner);
|
|
$json = json_encode($payload);
|
|
$hash = md5((string) $json);
|
|
|
|
if ($hash !== $lastHash) {
|
|
echo 'data: '.$json."\n\n";
|
|
$lastHash = $hash;
|
|
} else {
|
|
echo ": ping\n\n"; // heartbeat
|
|
}
|
|
@ob_flush();
|
|
@flush();
|
|
|
|
if (connection_aborted() || (time() - $start) >= 25) {
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
});
|
|
|
|
$response->headers->set('Content-Type', 'text/event-stream');
|
|
$response->headers->set('Cache-Control', 'no-cache');
|
|
$response->headers->set('Connection', 'keep-alive');
|
|
$response->headers->set('X-Accel-Buffering', 'no'); // disable nginx buffering
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Advance one line to the next kitchen state (queued → preparing → ready → served).
|
|
*/
|
|
public function bump(Request $request, PosSaleLine $line): JsonResponse
|
|
{
|
|
return response()->json($this->board->bumpLine($line, $this->ownerRef($request)));
|
|
}
|
|
}
|