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:
@@ -161,7 +161,7 @@ class NotificationMailServiceTest extends TestCase
|
||||
'precio' => 25,
|
||||
'has_tickets' => true,
|
||||
]);
|
||||
$purchase->items()->create([
|
||||
$purchaseItem = $purchase->items()->create([
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'nombre' => 'Entrada',
|
||||
'descripcion' => 'Entrada general',
|
||||
@@ -175,7 +175,7 @@ class NotificationMailServiceTest extends TestCase
|
||||
$ticket = Ticket::query()->create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
'ticket' => fake()->uuid(),
|
||||
'source_purchase_id' => $purchase->id,
|
||||
'source_purchase_item_id' => $purchaseItem->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
@@ -336,17 +336,28 @@ class AdminAppSaleControllerTest extends TestCase
|
||||
'precio' => 20000,
|
||||
'has_tickets' => true,
|
||||
]);
|
||||
$purchaseItem = $purchase->items()->create([
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'nombre' => $catalogItem->nombre,
|
||||
'descripcion' => $catalogItem->descripcion,
|
||||
'slug' => $catalogItem->slug,
|
||||
'item_nombre' => $catalogItem->nombre,
|
||||
'variant_attributes' => [],
|
||||
'cantidad' => 2,
|
||||
'precio_unitario' => 10000,
|
||||
'total' => 20000,
|
||||
]);
|
||||
$firstTicket = Ticket::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'ticket' => '11111111-1111-4111-8111-111111111111',
|
||||
'source_purchase_id' => $purchase->id,
|
||||
'source_purchase_item_id' => $purchaseItem->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'user_id' => $admin->id,
|
||||
]);
|
||||
$usedTicket = Ticket::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'ticket' => '22222222-2222-4222-8222-222222222222',
|
||||
'source_purchase_id' => $purchase->id,
|
||||
'source_purchase_item_id' => $purchaseItem->id,
|
||||
'source_catalog_item_id' => $catalogItem->id,
|
||||
'used_at' => now()->subMinute(),
|
||||
'user_id' => $admin->id,
|
||||
|
||||
@@ -297,7 +297,7 @@ class DesfilePuraTendenciaSeederTest extends TestCase
|
||||
->where('total', 0)
|
||||
->count());
|
||||
$this->assertSame(49, DB::table('tickets')
|
||||
->where('source_purchase_id', $purchase->id)
|
||||
->whereIn('source_purchase_item_id', (clone $items)->pluck('id'))
|
||||
->where('user_id', $user->id)
|
||||
->where('source_catalog_item_id', $catalogItemId)
|
||||
->count());
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Domains\Ticket\Exceptions\TicketGenerationException;
|
||||
use App\Domains\Ticket\Models\ValidityTime;
|
||||
use App\Domains\Ticket\Services\TicketGeneratorService;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
@@ -246,14 +247,59 @@ class TicketGeneratorServiceTest extends TestCase
|
||||
$purchase = $this->createPurchase($item, 1, $variant->id);
|
||||
|
||||
$purchase->markAsPaid();
|
||||
$purchaseItem = $purchase->items()->sole();
|
||||
|
||||
$this->assertDatabaseHas('tickets', [
|
||||
'source_purchase_id' => $purchase->id,
|
||||
'source_purchase_item_id' => $purchaseItem->id,
|
||||
'source_catalog_item_id' => $item->id,
|
||||
'source_variant_id' => $variant->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_bundle_tickets_reference_the_purchase_item_that_generated_them(): void
|
||||
{
|
||||
$component = $this->createTicketableItem('bundle-component');
|
||||
$bundle = $this->createBundle('purchased-bundle');
|
||||
$bundle->bundleComponents()->create([
|
||||
'component_catalog_item_id' => $component->id,
|
||||
'quantity' => 2,
|
||||
]);
|
||||
$purchase = $this->createPurchase($bundle, 2);
|
||||
$purchaseItem = $purchase->items()->sole();
|
||||
|
||||
$purchase->markAsPaid();
|
||||
|
||||
$this->assertCount(4, $purchaseItem->tickets);
|
||||
$this->assertTrue($purchaseItem->tickets->every(
|
||||
fn ($ticket): bool => $ticket->source_purchase_item_id === $purchaseItem->id
|
||||
&& $ticket->source_catalog_item_id === $component->id
|
||||
));
|
||||
$this->assertCount(4, $purchase->tickets);
|
||||
}
|
||||
|
||||
public function test_a_purchase_item_that_generated_tickets_cannot_be_deleted(): void
|
||||
{
|
||||
$item = $this->createTicketableItem('protected-purchase-item');
|
||||
$purchase = $this->createPurchase($item, 1);
|
||||
$purchase->markAsPaid();
|
||||
$purchaseItem = $purchase->items()->sole();
|
||||
|
||||
$this->expectException(QueryException::class);
|
||||
|
||||
$purchaseItem->delete();
|
||||
}
|
||||
|
||||
public function test_a_purchase_with_generated_tickets_cannot_be_deleted(): void
|
||||
{
|
||||
$item = $this->createTicketableItem('protected-purchase');
|
||||
$purchase = $this->createPurchase($item, 1);
|
||||
$purchase->markAsPaid();
|
||||
|
||||
$this->expectException(QueryException::class);
|
||||
|
||||
$purchase->delete();
|
||||
}
|
||||
|
||||
public function test_event_date_and_time_window_are_combined_in_the_same_and_group(): void
|
||||
{
|
||||
$item = $this->createTicketableItem('scheduled-meal');
|
||||
|
||||
Reference in New Issue
Block a user