Deploy Ladill POS / deploy (push) Successful in 36s
Split bills (restaurant tickets): - New pos_payments ledger; a tab is settled across one or more cash/Ladill Pay payments. The ticket shows total / paid / balance, an amount field with Full / ½ / ⅓ / ¼ helpers, and the payments taken. Partial payments keep the tab open; the sale finalises (and frees the table) only when the balance hits zero. Pay splits get their own checkout + callback (pos.payments.callback). Pipe online orders to the KDS: - POST /api/kitchen/orders — a first-party, service-keyed ingest (config pos.kitchen_api_keys, scoped by owner, idempotent by external_ref) that creates a paid, already-fired ticket (order_type=online, lines source=online). - The KDS feed is now payment-agnostic (any kitchen-active sale), so paid online orders sit on the board next to dine-in tabs and bump the same way; they're badged "online". Schema additive: pos_payments, pos_sales.external_ref. Suite green (14). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
5.1 KiB
PHP
74 lines
5.1 KiB
PHP
<x-app-layout title="Kitchen">
|
||
<div x-data="posKitchen(@js([
|
||
'feedUrl' => route('pos.kitchen.feed'),
|
||
'streamUrl' => route('pos.kitchen.stream'),
|
||
'bumpUrl' => route('pos.kitchen.bump', ['line' => '__ID__']),
|
||
]))" class="space-y-4">
|
||
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-lg font-semibold text-slate-900">Kitchen</h1>
|
||
<p class="mt-1 text-sm text-slate-500">Live tickets — tap an item to advance it. Refreshes automatically.</p>
|
||
</div>
|
||
<button type="button" @click="load()" class="rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-50">Refresh</button>
|
||
</div>
|
||
|
||
@if($stations->isNotEmpty())
|
||
<div class="flex flex-wrap gap-1.5">
|
||
<button type="button" @click="activeStation = ''" :class="activeStation === '' ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-600'"
|
||
class="rounded-full px-3 py-1 text-xs font-medium">All stations</button>
|
||
@foreach($stations as $station)
|
||
<button type="button" @click="activeStation = '{{ $station->id }}'"
|
||
:class="activeStation === '{{ $station->id }}' ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-600'"
|
||
class="rounded-full px-3 py-1 text-xs font-medium">{{ $station->name }}</button>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
|
||
<template x-if="loaded && visibleTickets.length === 0">
|
||
<div class="rounded-2xl border border-dashed border-slate-200 bg-white px-6 py-16 text-center">
|
||
<p class="text-sm text-slate-500">No active tickets. Fired orders will appear here.</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||
<template x-for="t in visibleTickets" :key="t.id">
|
||
<div class="flex flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
||
<div class="flex items-center justify-between border-b border-slate-100 bg-slate-50 px-4 py-2.5">
|
||
<div>
|
||
<p class="text-sm font-semibold text-slate-900" x-text="t.table ? t.table : t.order_type.replace('_',' ')"></p>
|
||
<p class="text-[11px] text-slate-400" x-text="t.reference"></p>
|
||
</div>
|
||
<span class="rounded-full bg-slate-900/5 px-2 py-0.5 text-xs font-medium text-slate-600" x-text="elapsed(t.sent_at)"></span>
|
||
</div>
|
||
<div class="flex-1 divide-y divide-slate-100">
|
||
<template x-for="line in t.lines" :key="line.id">
|
||
<div class="flex items-center justify-between gap-2 px-4 py-2.5"
|
||
:class="line.state === 'ready' ? 'bg-emerald-50' : (line.state === 'preparing' ? 'bg-amber-50/60' : '')">
|
||
<div class="min-w-0">
|
||
<p class="text-sm font-medium text-slate-900">
|
||
<span x-text="line.quantity + '×'"></span>
|
||
<span x-text="line.name"></span>
|
||
</p>
|
||
<p x-show="line.modifiers.length" x-cloak class="text-xs text-slate-500" x-text="line.modifiers.join(', ')"></p>
|
||
<p x-show="line.notes" x-cloak class="text-xs text-rose-600" x-text="line.notes"></p>
|
||
<div class="mt-0.5 flex items-center gap-1.5">
|
||
<span x-show="line.course" x-cloak class="rounded bg-slate-100 px-1.5 text-[10px] font-medium text-slate-500" x-text="line.course"></span>
|
||
<span x-show="line.station" x-cloak class="rounded bg-indigo-50 px-1.5 text-[10px] font-medium text-indigo-600" x-text="line.station"></span>
|
||
<span x-show="line.source === 'guest'" x-cloak class="rounded bg-violet-50 px-1.5 text-[10px] font-medium text-violet-700">guest</span>
|
||
<span x-show="line.source === 'online'" x-cloak class="rounded bg-blue-50 px-1.5 text-[10px] font-medium text-blue-700">online</span>
|
||
<span class="text-[11px] uppercase tracking-wide text-slate-400" x-text="line.state"></span>
|
||
</div>
|
||
</div>
|
||
<button type="button" @click="bump(line)"
|
||
class="shrink-0 rounded-lg border border-slate-200 bg-white px-2.5 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50"
|
||
x-text="nextLabel(line.state)"></button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</x-app-layout>
|