diff --git a/app/Http/Controllers/Pos/SaleController.php b/app/Http/Controllers/Pos/SaleController.php index b9d0535..e8462b5 100644 --- a/app/Http/Controllers/Pos/SaleController.php +++ b/app/Http/Controllers/Pos/SaleController.php @@ -44,6 +44,32 @@ class SaleController extends Controller return view('pos.sales.show', compact('sale', 'invoiceUrl')); } + public function cancel(Request $request, PosSale $sale): RedirectResponse + { + $this->authorizeOwner($request, $sale); + + try { + $this->sales->cancelSale($sale); + } catch (RuntimeException $e) { + return back()->with('error', $e->getMessage()); + } + + return redirect()->route('pos.sales.show', $sale)->with('success', 'Sale cancelled.'); + } + + public function destroy(Request $request, PosSale $sale): RedirectResponse + { + $this->authorizeOwner($request, $sale); + + try { + $this->sales->deleteSale($sale); + } catch (RuntimeException $e) { + return back()->with('error', $e->getMessage()); + } + + return redirect()->route('pos.sales.index')->with('success', 'Sale deleted.'); + } + public function callback(Request $request, PosSale $sale): RedirectResponse { $reference = trim((string) $request->query('reference', $sale->payment_reference ?? '')); diff --git a/app/Services/Pos/PosSaleService.php b/app/Services/Pos/PosSaleService.php index 3caf6c5..3f42d9a 100644 --- a/app/Services/Pos/PosSaleService.php +++ b/app/Services/Pos/PosSaleService.php @@ -446,6 +446,33 @@ class PosSaleService return $sale->fresh('lines'); } + /** Cancel a pending sale/ticket — frees its table and clears it from the kitchen. */ + public function cancelSale(PosSale $sale): void + { + if ($sale->status !== PosSale::STATUS_PENDING) { + throw new RuntimeException('Only pending sales can be cancelled.'); + } + + $sale->forceFill(['status' => PosSale::STATUS_CANCELLED])->save(); + $this->closeTicket($sale); + } + + /** Permanently delete an unpaid sale (and its lines, modifiers, payments). */ + public function deleteSale(PosSale $sale): void + { + if ($sale->status === PosSale::STATUS_PAID) { + throw new RuntimeException('Paid sales cannot be deleted.'); + } + + if ($sale->table_id) { + PosTable::where('id', $sale->table_id) + ->where('current_sale_id', $sale->id) + ->update(['status' => PosTable::STATUS_FREE, 'current_sale_id' => null]); + } + + $sale->delete(); + } + /** * Close a ticket once it's settled: free its table and mark kitchen done. */ diff --git a/resources/views/pos/sales/show.blade.php b/resources/views/pos/sales/show.blade.php index 5bfbfde..147693c 100644 --- a/resources/views/pos/sales/show.blade.php +++ b/resources/views/pos/sales/show.blade.php @@ -8,7 +8,15 @@
{{ $sale->created_at->format('M j, Y g:i A') }}
- + @php + $badge = match ($sale->status) { + 'paid' => 'bg-green-50 text-green-700', + 'cancelled' => 'bg-slate-100 text-slate-500', + 'failed' => 'bg-red-50 text-red-600', + default => 'bg-amber-50 text-amber-700', + }; + @endphp + {{ $sale->status }} @@ -45,6 +53,22 @@ Create invoice @endif + + @unless ($sale->isPaid()) +