From a71c80248c27c4f97af1ddb480cddeaf81d9a053 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Mon, 31 Aug 2026 10:57:25 -0300 Subject: [PATCH] feat(tickets): update TicketFilterFormService to use getForFilters method and enhance TicketFormService with historical data handling --- .../Services/TicketFilterFormService.php | 2 +- .../Forms/Services/TicketFormService.php | 106 +++++++++++++++++- ...AdminAppTicketFilterFormControllerTest.php | 56 ++++++++- 3 files changed, 160 insertions(+), 4 deletions(-) diff --git a/app/Domains/Forms/Services/TicketFilterFormService.php b/app/Domains/Forms/Services/TicketFilterFormService.php index e87611a..b10753c 100644 --- a/app/Domains/Forms/Services/TicketFilterFormService.php +++ b/app/Domains/Forms/Services/TicketFilterFormService.php @@ -34,7 +34,7 @@ class TicketFilterFormService /** @return list> */ private function fiestaFutbolInfantilFields(Tenant $tenant): array { - $form = $this->ticketFormService->get($tenant); + $form = $this->ticketFormService->getForFilters($tenant); return [ [ diff --git a/app/Domains/Forms/Services/TicketFormService.php b/app/Domains/Forms/Services/TicketFormService.php index c8690db..9c644de 100644 --- a/app/Domains/Forms/Services/TicketFormService.php +++ b/app/Domains/Forms/Services/TicketFormService.php @@ -6,6 +6,7 @@ use App\Domains\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\Variant; use App\Domains\Tenant\Models\Tenant; use App\Domains\Ticket\Models\Ticket; +use Illuminate\Database\Eloquent\Collection; class TicketFormService { @@ -69,15 +70,82 @@ class TicketFormService */ public function get(Tenant $tenant): array { - $categories = []; - $items = CatalogItem::query() ->where('tenant_code', $tenant->codigo) ->where('has_tickets', true) ->whereHas('category') + ->with($this->relations()) + ->orderBy('group_order') + ->orderBy('nombre') + ->get(); + + return $this->build($items); + } + + /** + * Return active catalog options plus soft-deleted sources still referenced by + * tickets, so historical tickets never become impossible to filter. + * + * @return array{ + * statuses: list, + * categories: list + * }> + * }> + * } + */ + public function getForFilters(Tenant $tenant): array + { + $historicalVariantIds = Ticket::query() + ->where('tenant_code', $tenant->codigo) + ->whereNotNull('source_variant_id') + ->distinct() + ->pluck('source_variant_id') + ->map(fn ($id): int => (int) $id) + ->all(); + $historicalCatalogItemIds = Ticket::query() + ->where('tenant_code', $tenant->codigo) + ->whereNotNull('source_catalog_item_id') + ->distinct() + ->pluck('source_catalog_item_id') + ->map(fn ($id): int => (int) $id) + ->merge( + Variant::withTrashed() + ->whereKey($historicalVariantIds) + ->pluck('catalog_item_id') + ->map(fn ($id): int => (int) $id), + ) + ->unique() + ->values() + ->all(); + + $items = CatalogItem::withTrashed() + ->where('tenant_code', $tenant->codigo) + ->whereHas('category') + ->where(function ($query) use ($historicalCatalogItemIds): void { + $query + ->where(function ($activeQuery): void { + $activeQuery + ->whereNull('catalog_items.deleted_at') + ->where('has_tickets', true); + }) + ->orWhereIn('catalog_items.id', $historicalCatalogItemIds); + }) ->with([ 'category', 'itemAttributes.attribute.options', + 'variants' => fn ($query) => $query + ->withTrashed() + ->where(function ($variantQuery) use ($historicalVariantIds): void { + $variantQuery + ->whereNull('variantes.deleted_at') + ->orWhereIn('variantes.id', $historicalVariantIds); + }), 'variants.definitions.itemAttribute.attribute.options', 'variants.eventDates', 'variants.eventDate', @@ -86,6 +154,28 @@ class TicketFormService ->orderBy('nombre') ->get(); + return $this->build($items); + } + + /** + * @param Collection $items + * @return array{ + * statuses: list, + * categories: list + * }> + * }> + * } + */ + private function build(Collection $items): array + { + $categories = []; + foreach ($items as $item) { $sourceCategory = trim((string) $item->category?->nombre); $categoryValue = mb_strtolower($sourceCategory); @@ -146,6 +236,18 @@ class TicketFormService ]; } + /** @return list */ + private function relations(): array + { + return [ + 'category', + 'itemAttributes.attribute.options', + 'variants.definitions.itemAttribute.attribute.options', + 'variants.eventDates', + 'variants.eventDate', + ]; + } + /** * @return listcreateFiestaFutbolInfantilTenant(); $this->grantTicketsMenu($tenant); - Sanctum::actingAs($this->createAdminAppUser($tenant)); + $admin = $this->createAdminAppUser($tenant); + Sanctum::actingAs($admin); $response = $this->getJson('/api/v1/adminapp/forms/tickets-filter') ->assertOk() @@ -154,6 +158,56 @@ class AdminAppTicketFilterFormControllerTest extends TestCase $this->assertContains('Cena', $schedules); } + public function test_a_deleted_food_variant_remains_in_the_filter_when_a_ticket_references_it(): void + { + $tenant = $this->createFiestaFutbolInfantilTenant(); + $this->grantTicketsMenu($tenant); + $admin = $this->createAdminAppUser($tenant); + Sanctum::actingAs($admin); + + $food = CatalogItem::query() + ->where('tenant_code', $tenant->codigo) + ->where('slug', 'comida') + ->with([ + 'itemAttributes.attribute.options', + 'variants.definitions.itemAttribute.attribute.options', + 'variants.eventDates', + 'variants.eventDate', + ]) + ->firstOrFail(); + $historicalVariant = $food->variants->first(function ($variant): bool { + return $variant->selectedEventDates()->first()?->date->format('d/m') === '12/10' + && $variant->selectionValues()->get('horario') === 'Cena'; + }); + $this->assertNotNull($historicalVariant); + + Ticket::query()->create([ + 'tenant_code' => $tenant->codigo, + 'ticket' => (string) Str::uuid(), + 'user_id' => $admin->id, + 'source_catalog_item_id' => $food->id, + 'source_variant_id' => $historicalVariant->id, + ]); + + $catalogService = app(CatalogService::class); + foreach ($food->variants as $variant) { + $catalogService->deleteVariant($variant); + } + + $this->assertTrue(CatalogItem::withTrashed()->findOrFail($food->id)->trashed()); + + $response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk(); + $foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas'); + $products = collect($foodCategory['children']['options']); + + $this->assertCount(1, $products); + $this->assertSame('12/10', $products->first()['label']); + $this->assertSame( + ['Cena'], + collect($products->first()['children']['options'])->pluck('label')->all(), + ); + } + private function createFiestaFutbolInfantilTenant(): Tenant { $tenant = $this->createTenant('fiesta_futbol_infantil');