Deploy Ladill POS / deploy (push) Successful in 35s
Builds on Phase 1 with the menu structure restaurants need: - Menu setup page (restaurant mode): categories, kitchen stations, and modifier groups with options (price deltas). - Products gain category, kitchen station, default course, and attached modifier groups (managed on the product form). - Ordering: the ticket groups the catalog by category and, when a product has modifier groups, opens a picker (respects min/max select) for options + notes + course before adding. Effective price = base + modifier deltas (resolved server-side; client prices are never trusted). - Fire-by-course: send the whole tab or just one course to the kitchen. - KDS: filter by station; each item shows its station, course, modifiers and notes. Schema additive (pos_categories, pos_stations, pos_modifier_groups, pos_modifiers, product↔group pivot, pos_sale_line_modifiers; category/station/course columns on products and lines). PosRestaurantTest covers modifiers, course and station routing; suite green (11 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.7 KiB
PHP
114 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Pos;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Controllers\Pos\Concerns\ScopesToAccount;
|
|
use App\Models\PosCategory;
|
|
use App\Models\PosModifier;
|
|
use App\Models\PosModifierGroup;
|
|
use App\Models\PosStation;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
class MenuController extends Controller
|
|
{
|
|
use ScopesToAccount;
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$owner = $this->ownerRef($request);
|
|
|
|
return view('pos.menu.index', [
|
|
'categories' => PosCategory::owned($owner)->orderBy('position')->orderBy('name')->get(),
|
|
'stations' => PosStation::owned($owner)->orderBy('position')->orderBy('name')->get(),
|
|
'groups' => PosModifierGroup::owned($owner)->with('modifiers')->orderBy('position')->orderBy('name')->get(),
|
|
]);
|
|
}
|
|
|
|
public function storeCategory(Request $request): RedirectResponse
|
|
{
|
|
$data = $request->validate(['name' => ['required', 'string', 'max:80']]);
|
|
PosCategory::create(['owner_ref' => $this->ownerRef($request), 'name' => $data['name']]);
|
|
|
|
return back()->with('success', 'Category added.');
|
|
}
|
|
|
|
public function destroyCategory(Request $request, PosCategory $category): RedirectResponse
|
|
{
|
|
$this->authorizeOwner($request, $category);
|
|
$category->delete();
|
|
|
|
return back()->with('success', 'Category removed.');
|
|
}
|
|
|
|
public function storeStation(Request $request): RedirectResponse
|
|
{
|
|
$data = $request->validate(['name' => ['required', 'string', 'max:80']]);
|
|
PosStation::create(['owner_ref' => $this->ownerRef($request), 'name' => $data['name']]);
|
|
|
|
return back()->with('success', 'Station added.');
|
|
}
|
|
|
|
public function destroyStation(Request $request, PosStation $station): RedirectResponse
|
|
{
|
|
$this->authorizeOwner($request, $station);
|
|
$station->delete();
|
|
|
|
return back()->with('success', 'Station removed.');
|
|
}
|
|
|
|
public function storeGroup(Request $request): RedirectResponse
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:80'],
|
|
'min_select' => ['nullable', 'integer', 'min:0', 'max:20'],
|
|
'max_select' => ['nullable', 'integer', 'min:0', 'max:20'],
|
|
]);
|
|
|
|
PosModifierGroup::create([
|
|
'owner_ref' => $this->ownerRef($request),
|
|
'name' => $data['name'],
|
|
'min_select' => $data['min_select'] ?? 0,
|
|
'max_select' => $data['max_select'] ?: null,
|
|
]);
|
|
|
|
return back()->with('success', 'Modifier group added.');
|
|
}
|
|
|
|
public function destroyGroup(Request $request, PosModifierGroup $group): RedirectResponse
|
|
{
|
|
$this->authorizeOwner($request, $group);
|
|
$group->delete();
|
|
|
|
return back()->with('success', 'Modifier group removed.');
|
|
}
|
|
|
|
public function storeModifier(Request $request, PosModifierGroup $group): RedirectResponse
|
|
{
|
|
$this->authorizeOwner($request, $group);
|
|
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:80'],
|
|
'price_delta' => ['nullable', 'numeric', 'min:-9999', 'max:9999'],
|
|
]);
|
|
|
|
$group->modifiers()->create([
|
|
'owner_ref' => $group->owner_ref,
|
|
'name' => $data['name'],
|
|
'price_delta_minor' => (int) round(((float) ($data['price_delta'] ?? 0)) * 100),
|
|
]);
|
|
|
|
return back()->with('success', 'Option added.');
|
|
}
|
|
|
|
public function destroyModifier(Request $request, PosModifier $modifier): RedirectResponse
|
|
{
|
|
$this->authorizeOwner($request, $modifier);
|
|
$modifier->delete();
|
|
|
|
return back()->with('success', 'Option removed.');
|
|
}
|
|
}
|