Allow cancelling and deleting pending POS sales
Deploy Ladill POS / deploy (push) Successful in 37s

On a sale's detail page, unpaid sales (retail or restaurant) can now be
cancelled or deleted:
- Cancel (pending only) marks it cancelled and frees its table / clears it from
  the kitchen (PosSaleService::cancelSale).
- Delete removes the sale and cascades its lines, modifiers and payments, freeing
  the table first (deleteSale). Paid sales are protected — they can't be deleted.
Status badge now distinguishes cancelled/failed. Covered by PosRestaurantTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-25 21:52:51 +00:00
co-authored by Claude Opus 4.8
parent 5d8e185223
commit f64a28cb21
5 changed files with 112 additions and 1 deletions
+32
View File
@@ -268,6 +268,38 @@ class PosRestaurantTest extends TestCase
$this->assertSame('retail', PosLocation::owned($user->public_id)->first()->service_style);
}
public function test_pending_ticket_can_be_cancelled_then_deleted(): void
{
$user = $this->user();
[, $table, $product] = $this->restaurant($user);
$this->actingAs($user)->post(route('pos.tickets.open'), ['order_type' => 'dine_in', 'table_id' => $table->id])->assertRedirect();
$sale = PosSale::where('owner_ref', $user->public_id)->firstOrFail();
$this->actingAs($user)->postJson(route('pos.tickets.lines.add', $sale), ['product_id' => $product->id, 'quantity' => 1])->assertOk();
// Cancel — frees the table.
$this->actingAs($user)->post(route('pos.sales.cancel', $sale))->assertRedirect(route('pos.sales.show', $sale));
$this->assertSame(PosSale::STATUS_CANCELLED, $sale->fresh()->status);
$this->assertSame(PosTable::STATUS_FREE, $table->fresh()->status);
// Delete — gone (cascades lines).
$this->actingAs($user)->delete(route('pos.sales.destroy', $sale))->assertRedirect(route('pos.sales.index'));
$this->assertNull(PosSale::find($sale->id));
$this->assertSame(0, $sale->lines()->count());
}
public function test_paid_sale_cannot_be_deleted(): void
{
$user = $this->user();
$sale = PosSale::create([
'owner_ref' => $user->public_id, 'reference' => 'POS-PAID1', 'status' => PosSale::STATUS_PAID,
'payment_method' => 'cash', 'subtotal_minor' => 1000, 'total_minor' => 1000, 'currency' => 'GHS', 'paid_at' => now(),
]);
$this->actingAs($user)->delete(route('pos.sales.destroy', $sale));
$this->assertNotNull(PosSale::find($sale->id));
}
public function test_cannot_open_two_tabs_on_one_table(): void
{
$user = $this->user();