PosStation::owned($this->ownerRef($request))->orderBy('name')->get(['id', 'name']), ]); } /** * Active kitchen tickets — open sales with fired-but-not-served lines. */ public function feed(Request $request): JsonResponse { $owner = $this->ownerRef($request); $sales = PosSale::owned($owner) ->openTickets() ->where('kitchen_status', PosSale::KITCHEN_ACTIVE) ->with(['table', 'lines' => fn ($q) => $q->orderBy('position')->with('modifiers', 'station')]) ->orderBy('kitchen_sent_at') ->get(); $tickets = $sales->map(function (PosSale $sale) { $lines = $sale->lines ->filter(fn (PosSaleLine $l) => in_array($l->kitchen_state, [ PosSaleLine::KITCHEN_QUEUED, PosSaleLine::KITCHEN_PREPARING, PosSaleLine::KITCHEN_READY, ], true)) ->values(); if ($lines->isEmpty()) { return null; } return [ 'id' => $sale->id, 'reference' => $sale->reference, 'order_type' => $sale->order_type, 'table' => $sale->table?->label, 'sent_at' => optional($sale->kitchen_sent_at)->toIso8601String(), 'lines' => $lines->map(fn (PosSaleLine $l) => [ 'id' => $l->id, 'name' => $l->name, 'quantity' => $l->quantity, 'notes' => $l->notes, 'course' => $l->course, 'station' => $l->station?->name, 'station_id' => $l->station_id, 'source' => $l->source, 'modifiers' => $l->modifiers->map(fn ($m) => $m->name)->all(), 'state' => $l->kitchen_state, ])->all(), ]; })->filter()->values(); return response()->json(['tickets' => $tickets, 'server_time' => now()->toIso8601String()]); } /** * Advance one line to the next kitchen state (queued → preparing → ready → served). */ public function bump(Request $request, PosSaleLine $line): JsonResponse { $owner = $this->ownerRef($request); abort_unless($line->sale && $line->sale->owner_ref === $owner, 404); $flow = PosSaleLine::KITCHEN_FLOW; $idx = array_search($line->kitchen_state, $flow, true); $next = $idx === false ? PosSaleLine::KITCHEN_PREPARING : ($flow[$idx + 1] ?? PosSaleLine::KITCHEN_SERVED); $line->forceFill(['kitchen_state' => $next])->save(); // When every fired line is served, the ticket leaves the kitchen board. $sale = $line->sale; $remaining = $sale->lines() ->whereIn('kitchen_state', [PosSaleLine::KITCHEN_QUEUED, PosSaleLine::KITCHEN_PREPARING, PosSaleLine::KITCHEN_READY]) ->count(); if ($remaining === 0) { $sale->forceFill(['kitchen_status' => PosSale::KITCHEN_SERVED])->save(); } return response()->json(['state' => $next]); } }