Add restaurant/café mode: floor, open tabs, and kitchen display (Phase 1)
Deploy Ladill POS / deploy (push) Successful in 30s
Deploy Ladill POS / deploy (push) Successful in 30s
Gated by a per-location service_style (retail | restaurant). Restaurant mode adds: - Floor screen with tables (areas, seats, status) — tap a free table to open a dine-in tab; takeaway/counter orders open from the same screen. - Open tickets (tabs): a sale stays pending while items are added; lines persist as you go (posTicket Alpine component posts each change to the server). - Send-to-kitchen fires un-sent lines; a polling Kitchen Display (KDS) shows active tickets and bumps items queued → preparing → ready → served. - Settlement reuses the existing cash / Ladill Pay flow; paying closes the tab and frees the table (PosSaleService::closeTicket, wired into both pay paths). - Settings gains the mode toggle and a tables manager. Schema is additive (new pos_tables; service_style on locations; order/kitchen columns on sales + lines). Retail flow is untouched. Sidebar surfaces Floor + Kitchen only in restaurant mode. New PosRestaurantTest covers the dine-in lifecycle end to end; suite green (10 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e9a0c92308
commit
d9e4b6e06e
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Pos;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Pos\Concerns\ScopesToAccount;
|
||||
use App\Models\PosLocation;
|
||||
use App\Models\PosTable;
|
||||
use App\Services\Import\CrmProductImportService;
|
||||
use App\Services\Import\MerchantCatalogImportService;
|
||||
use App\Services\Pos\PosLocationService;
|
||||
@@ -20,11 +22,13 @@ class SettingsController extends Controller
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$location = $this->locations->ensureDefault($this->ownerRef($request));
|
||||
$owner = $this->ownerRef($request);
|
||||
$location = $this->locations->ensureDefault($owner);
|
||||
|
||||
return view('pos.settings', [
|
||||
'location' => $location,
|
||||
'merchantImportEnabled' => (bool) config('pos.merchant_import_enabled', true),
|
||||
'tables' => PosTable::owned($owner)->orderBy('area')->orderBy('position')->orderBy('label')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -33,6 +37,7 @@ class SettingsController extends Controller
|
||||
$data = $request->validate([
|
||||
'name' => ['required', 'string', 'max:120'],
|
||||
'currency' => ['required', 'string', 'size:3'],
|
||||
'service_style' => ['required', 'in:retail,restaurant'],
|
||||
'receipt_footer' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
@@ -40,12 +45,50 @@ class SettingsController extends Controller
|
||||
$location->update([
|
||||
'name' => $data['name'],
|
||||
'currency' => strtoupper($data['currency']),
|
||||
'service_style' => $data['service_style'],
|
||||
'receipt_footer' => $data['receipt_footer'] ?? null,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function storeTable(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'area' => ['nullable', 'string', 'max:60'],
|
||||
'label' => ['required', 'string', 'max:60'],
|
||||
'seats' => ['nullable', 'integer', 'min:1', 'max:99'],
|
||||
]);
|
||||
|
||||
$owner = $this->ownerRef($request);
|
||||
$location = $this->locations->ensureDefault($owner);
|
||||
|
||||
PosTable::create([
|
||||
'owner_ref' => $owner,
|
||||
'location_id' => $location->id,
|
||||
'area' => $data['area'] ?? null,
|
||||
'label' => $data['label'],
|
||||
'seats' => $data['seats'] ?? 2,
|
||||
'status' => PosTable::STATUS_FREE,
|
||||
'position' => (int) PosTable::owned($owner)->max('position') + 1,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Table added.');
|
||||
}
|
||||
|
||||
public function destroyTable(Request $request, PosTable $table): RedirectResponse
|
||||
{
|
||||
$this->authorizeOwner($request, $table);
|
||||
|
||||
if (! $table->isFree()) {
|
||||
return back()->with('error', 'Settle the open ticket before removing this table.');
|
||||
}
|
||||
|
||||
$table->delete();
|
||||
|
||||
return back()->with('success', 'Table removed.');
|
||||
}
|
||||
|
||||
public function importCrm(Request $request, CrmProductImportService $import): RedirectResponse
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user