Deploy Ladill POS / deploy (push) Successful in 56s
Token-gated full-screen customer view shows order, payment QR, tips, signature, receipt, and promo phases while the till keeps operator UI.
147 lines
4.9 KiB
PHP
147 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Public;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\PosCustomerDisplay;
|
|
use App\Services\Pos\CustomerDisplayService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class CustomerDisplayController extends Controller
|
|
{
|
|
public function __construct(private CustomerDisplayService $displays) {}
|
|
|
|
public function show(string $token): View
|
|
{
|
|
$display = $this->find($token);
|
|
|
|
return view('pos.customer-display', [
|
|
'display' => $display,
|
|
'state' => $this->displays->publicState($display),
|
|
'stateUrl' => route('pos.customer-display.state', ['token' => $token]),
|
|
'streamUrl' => route('pos.customer-display.stream', ['token' => $token]),
|
|
'actionUrl' => route('pos.customer-display.action', ['token' => $token]),
|
|
]);
|
|
}
|
|
|
|
public function state(string $token): JsonResponse
|
|
{
|
|
$display = $this->find($token);
|
|
|
|
return response()->json($this->displays->publicState($display));
|
|
}
|
|
|
|
public function stream(Request $request, string $token): StreamedResponse
|
|
{
|
|
$display = $this->find($token);
|
|
$id = $display->id;
|
|
$request->session()->save();
|
|
|
|
$response = new StreamedResponse(function () use ($id) {
|
|
while (ob_get_level() > 0) {
|
|
@ob_end_flush();
|
|
}
|
|
@set_time_limit(0);
|
|
|
|
$start = time();
|
|
$lastHash = null;
|
|
|
|
while (true) {
|
|
$display = PosCustomerDisplay::query()->find($id);
|
|
if (! $display) {
|
|
echo "event: end\ndata: {}\n\n";
|
|
break;
|
|
}
|
|
|
|
$payload = $this->displays->publicState($display);
|
|
$json = json_encode($payload);
|
|
$hash = md5((string) $json);
|
|
|
|
if ($hash !== $lastHash) {
|
|
echo 'data: '.$json."\n\n";
|
|
$lastHash = $hash;
|
|
} else {
|
|
echo ": ping\n\n";
|
|
}
|
|
@ob_flush();
|
|
@flush();
|
|
|
|
if (connection_aborted() || (time() - $start) >= 25) {
|
|
break;
|
|
}
|
|
usleep(400000);
|
|
}
|
|
});
|
|
|
|
$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');
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function action(Request $request, string $token): JsonResponse
|
|
{
|
|
$display = $this->find($token);
|
|
|
|
$data = $request->validate([
|
|
'type' => ['required', 'string', 'in:tip,signature,receipt_email,payment_option'],
|
|
'tip_percent' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
|
'tip_minor' => ['nullable', 'integer', 'min:0'],
|
|
'signature_data_url' => ['nullable', 'string', 'max:500000'],
|
|
'email' => ['nullable', 'email', 'max:255'],
|
|
'payment_option' => ['nullable', 'string', 'max:40'],
|
|
]);
|
|
|
|
$action = ['type' => $data['type']];
|
|
|
|
if ($data['type'] === 'tip') {
|
|
$action['tip_percent'] = $data['tip_percent'] ?? null;
|
|
$action['tip_minor'] = $data['tip_minor'] ?? null;
|
|
// Reflect selection on the screen immediately.
|
|
$payload = $display->payload ?? [];
|
|
$tip = $payload['tip'] ?? [];
|
|
$tip['selected_percent'] = $data['tip_percent'] ?? null;
|
|
$tip['selected_minor'] = $data['tip_minor'] ?? null;
|
|
$payload['tip'] = $tip;
|
|
$display->update(['payload' => $payload, 'last_pushed_at' => now()]);
|
|
}
|
|
|
|
if ($data['type'] === 'signature') {
|
|
$action['signature_data_url'] = $data['signature_data_url'] ?? null;
|
|
$payload = $display->payload ?? [];
|
|
$sig = $payload['signature'] ?? [];
|
|
$sig['data_url'] = $data['signature_data_url'] ?? null;
|
|
$payload['signature'] = $sig;
|
|
$display->update(['payload' => $payload, 'last_pushed_at' => now()]);
|
|
}
|
|
|
|
if ($data['type'] === 'receipt_email') {
|
|
$action['email'] = $data['email'] ?? null;
|
|
}
|
|
|
|
if ($data['type'] === 'payment_option') {
|
|
$action['payment_option'] = $data['payment_option'] ?? null;
|
|
}
|
|
|
|
$this->displays->recordCustomerAction($display->fresh(), $action);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'state' => $this->displays->publicState($display->fresh()),
|
|
]);
|
|
}
|
|
|
|
private function find(string $token): PosCustomerDisplay
|
|
{
|
|
return PosCustomerDisplay::query()
|
|
->where('token', $token)
|
|
->with('location')
|
|
->firstOrFail();
|
|
}
|
|
}
|