Squashed commit of the following:

commit 6ae625d4fdc11989950ef0678db62fdb52be7c8c
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Sep 2 09:46:18 2026 -0300

    refactor(tickets): enhance search functionality for tickets by client names and formatted amounts

commit 098e525d32be9cb6487bc01a3d120aa0f2386a15
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Sep 2 09:09:30 2026 -0300

    fix(tickets): protect referenced purchase items

commit 210db17f2f6a6a4a509297b773a2b5d3e3149a3d
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Sep 2 08:59:24 2026 -0300

    test(tickets): cover purchase item references

commit 6a004713c4bad622dfa5e26394458925db1cb5a7
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Sep 2 08:59:02 2026 -0300

    refactor(tickets): reference purchase items directly
This commit is contained in:
2026-09-02 09:47:04 -03:00
parent 836aa1afd7
commit ee906d2d2e
18 changed files with 504 additions and 85 deletions

View File

@@ -79,7 +79,7 @@ class AdminAppTicketControllerTest extends TestCase
->assertJsonPath('meta.total', 1);
}
public function test_it_supports_id_and_uuid_search(): void
public function test_it_searches_by_id_and_does_not_search_by_uuid(): void
{
$tenant = $this->createTenant('fiesta_futbol_infantil');
$admin = $this->createAdminAppUser($tenant);
@@ -95,9 +95,98 @@ class AdminAppTicketControllerTest extends TestCase
->assertJsonPath('data.0.id', $matching->id);
$this->getJson('/api/v1/adminapp/tenant/tickets?q='.substr($matching->ticket, 0, 8))
->assertOk()
->assertJsonCount(0, 'data');
}
public function test_it_searches_by_visible_client_and_scanner_names(): void
{
$tenant = $this->createTenant('fiesta_futbol_infantil');
$admin = $this->createAdminAppUser($tenant);
$client = User::factory()->create([
'tenant_codigo' => $tenant->codigo,
'nombre_apellido' => 'María Cliente',
]);
$scanner = $this->createAdminAppUser($tenant);
$scanner->update(['nombre_apellido' => 'Carlos Inspector']);
$this->grantTicketsMenu($tenant);
Sanctum::actingAs($admin);
$clientTicket = $this->createTicket($tenant, $client);
$scannerTicket = $this->createTicket($tenant, $admin, [
'scanner_user_id' => $scanner->id,
'used_at' => now(),
]);
$this->createTicket($tenant, $admin);
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query(['q' => 'maría']))
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.ticket', $matching->ticket);
->assertJsonPath('data.0.id', $clientTicket->id);
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query(['q' => 'INSPECTOR']))
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.id', $scannerTicket->id);
}
public function test_it_searches_by_order_number_and_formatted_unit_amount(): void
{
$tenant = $this->createTenant('fiesta_futbol_infantil');
$admin = $this->createAdminAppUser($tenant);
$this->grantTicketsMenu($tenant);
Sanctum::actingAs($admin);
foreach (range(1, 10) as $number) {
$this->createPurchase($tenant, $admin, now()->subMinutes($number)->toDateTimeString());
}
$item = CatalogItem::query()->create([
'tenant_code' => $tenant->codigo,
'slug' => 'entrada-busqueda',
'nombre' => 'Entrada búsqueda',
'precio' => '1234.50',
]);
$purchase = Purchase::query()->create([
'tenant_codigo' => $tenant->codigo,
'user_id' => $admin->id,
'status' => Purchase::STATUS_PAID,
'nombre_apellido' => 'Cliente de la compra',
'total' => '1234.50',
]);
$purchaseItem = PurchaseItem::query()->create([
'compra_id' => $purchase->id,
'source_catalog_item_id' => $item->id,
'nombre' => $item->nombre,
'descripcion' => '',
'slug' => $item->slug,
'item_nombre' => $item->nombre,
'cantidad' => 1,
'precio_unitario' => '1234.50',
'total' => '1234.50',
]);
$matching = $this->createTicket($tenant, $admin, [
'source_purchase_item_id' => $purchaseItem->id,
'source_catalog_item_id' => $item->id,
]);
$this->createTicket($tenant, $admin);
$this->getJson("/api/v1/adminapp/tenant/tickets?q={$purchase->id}")
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.id', $matching->id);
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query(['q' => 'cliente de la compra']))
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.id', $matching->id);
foreach (['1234,50', '$1.234,50', '1,234.50'] as $amount) {
$this->getJson('/api/v1/adminapp/tenant/tickets?'.http_build_query(['q' => $amount]))
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.id', $matching->id);
}
}
public function test_it_sorts_by_ticket_id_before_paginating(): void
@@ -219,7 +308,7 @@ class AdminAppTicketControllerTest extends TestCase
'nombre_apellido' => 'Nombre Apellido',
'total' => '8000.00',
]);
PurchaseItem::query()->create([
$purchaseItem = PurchaseItem::query()->create([
'compra_id' => $purchase->id,
'source_catalog_item_id' => $item->id,
'source_variant_id' => $variant->id,
@@ -232,7 +321,7 @@ class AdminAppTicketControllerTest extends TestCase
'total' => '8000.00',
]);
$ticket = $this->createTicket($tenant, $admin, [
'source_purchase_id' => $purchase->id,
'source_purchase_item_id' => $purchaseItem->id,
'source_catalog_item_id' => $item->id,
'source_variant_id' => $variant->id,
'scanner_user_id' => $admin->id,
@@ -242,6 +331,7 @@ class AdminAppTicketControllerTest extends TestCase
$this->getJson('/api/v1/adminapp/tenant/tickets')
->assertOk()
->assertJsonPath('data.0.id', $ticket->id)
->assertJsonPath('data.0.source_purchase_item_id', $purchaseItem->id)
->assertJsonPath('data.0.order_number', $purchase->id)
->assertJsonPath('data.0.product', 'Remera')
->assertJsonPath('data.0.amount', '8000.00')
@@ -289,20 +379,23 @@ class AdminAppTicketControllerTest extends TestCase
$matchingPurchase = $this->createPurchase($tenant, $admin, '2026-08-20 10:00:00');
$otherPurchase = $this->createPurchase($tenant, $admin, '2026-08-21 10:00:00');
$dinnerItem = $this->createPurchaseItem($matchingPurchase, $food, $dinner);
$lunchItem = $this->createPurchaseItem($matchingPurchase, $food, $lunch);
$otherDinnerItem = $this->createPurchaseItem($otherPurchase, $food, $otherDinner);
$matching = $this->createTicket($tenant, $admin, [
'source_purchase_id' => $matchingPurchase->id,
'source_purchase_item_id' => $dinnerItem->id,
'source_catalog_item_id' => $food->id,
'source_variant_id' => $dinner->id,
'used_at' => now(),
]);
$this->createTicket($tenant, $admin, [
'source_purchase_id' => $matchingPurchase->id,
'source_purchase_item_id' => $lunchItem->id,
'source_catalog_item_id' => $food->id,
'source_variant_id' => $lunch->id,
'used_at' => now(),
]);
$this->createTicket($tenant, $admin, [
'source_purchase_id' => $otherPurchase->id,
'source_purchase_item_id' => $otherDinnerItem->id,
'source_catalog_item_id' => $food->id,
'source_variant_id' => $otherDinner->id,
'used_at' => now(),
@@ -494,6 +587,22 @@ class AdminAppTicketControllerTest extends TestCase
return $purchase;
}
private function createPurchaseItem(Purchase $purchase, CatalogItem $item, Variant $variant): PurchaseItem
{
return $purchase->items()->create([
'source_catalog_item_id' => $item->id,
'source_variant_id' => $variant->id,
'nombre' => $item->nombre,
'descripcion' => $item->descripcion,
'slug' => $item->slug,
'item_nombre' => $item->nombre,
'variant_attributes' => [],
'cantidad' => 1,
'precio_unitario' => 0,
'total' => 0,
]);
}
private function grantTicketsMenu(Tenant $tenant): void
{
$menu = Menu::query()->create([