Split bills + kitchen ingest for online orders
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>
This commit is contained in:
isaacclad
2026-06-24 23:49:01 +00:00
co-authored by Claude Opus 4.8
parent 6c42f18186
commit 9780591e74
15 changed files with 521 additions and 9 deletions
+9 -2
View File
@@ -1,4 +1,11 @@
<?php
// Ladill POS is a web-only in-store register; it exposes no JSON API.
// (The mobile/QR API surface from the upstream fork was removed.)
use App\Http\Controllers\Api\KitchenIngestController;
use Illuminate\Support\Facades\Route;
// First-party kitchen ingest — sibling Ladill apps (e.g. Merchant) push paid
// online/QR orders onto an account's Kitchen Display. Auth via a per-service
// Bearer key (config pos.kitchen_api_keys); scoped by the `owner` parameter.
Route::post('/kitchen/orders', [KitchenIngestController::class, 'store'])
->middleware('throttle:120,1')
->name('api.kitchen.orders');
+1
View File
@@ -30,6 +30,7 @@ Route::get('/sso/platform-signed-out', [SsoLoginController::class, 'platformSign
Route::get('/signed-out', fn () => auth()->check() ? redirect()->route('pos.dashboard') : view('auth.signed-out'))->name('pos.signed-out');
Route::get('/sales/{sale}/callback', [SaleController::class, 'callback'])->name('pos.sales.callback');
Route::get('/payments/callback', [TicketController::class, 'paymentCallback'])->name('pos.payments.callback');
// Public table-QR ordering (no auth — scoped by the table short_code).
Route::get('/t/{code}', [TableOrderController::class, 'menu'])->name('pos.table.menu');