diff --git a/app/Domains/Forms/Controllers/AdminApp/TicketFilterFormController.php b/app/Domains/Forms/Controllers/AdminApp/TicketFilterFormController.php new file mode 100644 index 0000000..d6c1201 --- /dev/null +++ b/app/Domains/Forms/Controllers/AdminApp/TicketFilterFormController.php @@ -0,0 +1,20 @@ +user('sanctum')->tenant()->firstOrFail(); + + return TicketFilterFormResource::make($this->formService->get($tenant)); + } +} diff --git a/app/Domains/Forms/Resources/TicketFilterFormResource.php b/app/Domains/Forms/Resources/TicketFilterFormResource.php new file mode 100644 index 0000000..f1f4aa7 --- /dev/null +++ b/app/Domains/Forms/Resources/TicketFilterFormResource.php @@ -0,0 +1,20 @@ + */ + public function toArray(Request $request): array + { + return [ + 'code' => $this->resource['code'], + 'action' => $this->resource['action'], + 'method' => $this->resource['method'], + 'fields' => $this->resource['fields'], + ]; + } +} diff --git a/app/Domains/Forms/Services/TicketFilterFormService.php b/app/Domains/Forms/Services/TicketFilterFormService.php new file mode 100644 index 0000000..e87611a --- /dev/null +++ b/app/Domains/Forms/Services/TicketFilterFormService.php @@ -0,0 +1,118 @@ + */ + public function get(Tenant $tenant): array + { + $fields = $this->commonFields(); + + if ($tenant->codigo === self::FIESTA_FUTBOL_INFANTIL) { + $fields = [ + ...$this->fiestaFutbolInfantilFields($tenant), + ...$fields, + ]; + } + + return [ + 'code' => 'tickets_filter', + 'action' => '/api/v1/adminapp/tenant/tickets', + 'method' => 'GET', + 'fields' => $fields, + ]; + } + + /** @return list> */ + private function fiestaFutbolInfantilFields(Tenant $tenant): array + { + $form = $this->ticketFormService->get($tenant); + + return [ + [ + 'name' => 'category', + 'label' => 'Categoría', + 'type' => 'select', + 'required' => false, + 'default' => null, + 'placeholder' => 'Categoría', + 'options' => array_map( + fn (array $category): array => [ + 'value' => $category['value'], + 'label' => $category['label'], + 'children' => [ + 'field' => 'product', + 'disabled' => $category['products'] === [], + 'options' => array_map( + fn (array $product): array => [ + 'value' => $product['value'], + 'label' => $product['label'], + 'children' => [ + 'field' => 'type', + 'disabled' => $product['types'] === [], + 'options' => $product['types'], + ], + ], + $category['products'], + ), + ], + ], + $form['categories'], + ), + ], + $this->dependentSelect('product', 'Producto', 'category'), + $this->dependentSelect('type', 'Tipo', 'product'), + ]; + } + + /** @return list> */ + private function commonFields(): array + { + return [ + [ + 'name' => 'date', + 'label' => 'Fecha', + 'type' => 'date', + 'required' => false, + 'default' => null, + ], + [ + 'name' => 'status', + 'label' => 'Estado', + 'type' => 'select', + 'required' => false, + 'default' => null, + 'placeholder' => 'Estado', + 'options' => [ + ['value' => Ticket::STATUS_ACTIVE, 'label' => 'Activo'], + ['value' => Ticket::STATUS_USED, 'label' => 'Usado'], + ['value' => Ticket::STATUS_EXPIRED, 'label' => 'Vencido'], + ], + ], + ]; + } + + /** @return array */ + private function dependentSelect(string $name, string $label, string $dependency): array + { + return [ + 'name' => $name, + 'label' => $label, + 'type' => 'select', + 'required' => false, + 'default' => null, + 'placeholder' => $label, + 'depends_on' => $dependency, + 'disabled' => true, + 'options' => [], + ]; + } +} diff --git a/app/Domains/Forms/routes/adminapp.php b/app/Domains/Forms/routes/adminapp.php index 21a92d1..70f9ee3 100644 --- a/app/Domains/Forms/routes/adminapp.php +++ b/app/Domains/Forms/routes/adminapp.php @@ -5,6 +5,7 @@ use App\Domains\Forms\Controllers\AdminApp\FoodFormController; use App\Domains\Forms\Controllers\AdminApp\MerchandiseFormController; use App\Domains\Forms\Controllers\AdminApp\SaleFormController; use App\Domains\Forms\Controllers\AdminApp\StaffFormController; +use App\Domains\Forms\Controllers\AdminApp\TicketFilterFormController; use App\Domains\Forms\Controllers\AdminApp\TicketFormController; use Illuminate\Support\Facades\Route; @@ -14,6 +15,9 @@ Route::prefix('v1/adminapp/forms') Route::get('event', EventFormController::class); Route::get('sale', SaleFormController::class); Route::get('staff', StaffFormController::class); + Route::get('tickets-filter', TicketFilterFormController::class) + ->middleware('tenant.menu:adminapp.tickets') + ->name('adminapp.forms.tickets-filter'); Route::get( 'fiesta-futbol-infantil/ticket', TicketFormController::class diff --git a/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php b/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php new file mode 100644 index 0000000..55f8035 --- /dev/null +++ b/tests/Feature/Forms/AdminAppTicketFilterFormControllerTest.php @@ -0,0 +1,213 @@ +seed(AuthorizationSeeder::class); + } + + public function test_authentication_is_required(): void + { + $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertUnauthorized(); + } + + public function test_the_tenant_must_have_the_tickets_menu(): void + { + $tenant = $this->createTenant('tenant_without_tickets'); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertNotFound(); + } + + public function test_it_returns_the_common_ticket_filter_fields_for_other_tenants(): void + { + $tenant = $this->createTenant('another_tenant'); + $this->grantTicketsMenu($tenant); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $this->getJson('/api/v1/adminapp/forms/tickets-filter') + ->assertOk() + ->assertExactJson([ + 'data' => [ + 'code' => 'tickets_filter', + 'action' => '/api/v1/adminapp/tenant/tickets', + 'method' => 'GET', + 'fields' => [ + [ + 'name' => 'date', + 'label' => 'Fecha', + 'type' => 'date', + 'required' => false, + 'default' => null, + ], + [ + 'name' => 'status', + 'label' => 'Estado', + 'type' => 'select', + 'required' => false, + 'default' => null, + 'placeholder' => 'Estado', + 'options' => [ + ['value' => 'active', 'label' => 'Activo'], + ['value' => 'used', 'label' => 'Usado'], + ['value' => 'expired', 'label' => 'Vencido'], + ], + ], + ], + ], + ]); + } + + public function test_it_returns_the_complete_nested_form_for_fiesta_futbol_infantil(): void + { + $tenant = $this->createFiestaFutbolInfantilTenant(); + $this->grantTicketsMenu($tenant); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $response = $this->getJson('/api/v1/adminapp/forms/tickets-filter') + ->assertOk() + ->assertJsonPath('data.fields.0.name', 'category') + ->assertJsonPath('data.fields.1.name', 'product') + ->assertJsonPath('data.fields.1.depends_on', 'category') + ->assertJsonPath('data.fields.2.name', 'type') + ->assertJsonPath('data.fields.2.depends_on', 'product') + ->assertJsonPath('data.fields.3.name', 'date') + ->assertJsonPath('data.fields.4.name', 'status'); + + $categories = collect($response->json('data.fields.0.options')); + $this->assertSame( + ['Entradas', 'Camping', 'Comida', 'Merchandising'], + $categories->pluck('label')->all(), + ); + + $entries = $categories->firstWhere('value', 'entradas'); + $this->assertSame('Abono', $entries['children']['options'][0]['label']); + $this->assertTrue($entries['children']['options'][0]['children']['disabled']); + + $camping = $categories->firstWhere('value', 'alojamientos'); + $this->assertSame( + ['Carpa', 'Motorhome'], + collect($camping['children']['options'])->pluck('label')->all(), + ); + $this->assertTrue($camping['children']['options'][0]['children']['disabled']); + + $merchandise = $categories->firstWhere('value', 'merchandising'); + $this->assertSame('Camiseta', $merchandise['children']['options'][0]['label']); + $this->assertSame( + ['Verde', 'Blanco'], + collect($merchandise['children']['options'][0]['children']['options'])->pluck('label')->all(), + ); + } + + public function test_a_food_schedule_is_only_returned_when_a_variant_exists_for_the_date(): void + { + $tenant = $this->createFiestaFutbolInfantilTenant(); + $this->grantTicketsMenu($tenant); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $date = $tenant->eventDates()->orderByDesc('date')->firstOrFail(); + $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(); + + $food->variants + ->filter(fn ($variant): bool => $variant->selectedEventDates()->contains('id', $date->id) + && $variant->selectionValues()->get('horario') === 'Almuerzo') + ->each->delete(); + + $response = $this->getJson('/api/v1/adminapp/forms/tickets-filter')->assertOk(); + $foodCategory = collect($response->json('data.fields.0.options'))->firstWhere('value', 'comidas'); + $dateProduct = collect($foodCategory['children']['options']) + ->firstWhere('value', (string) $date->id); + $schedules = collect($dateProduct['children']['options'])->pluck('value'); + + $this->assertNotContains('Almuerzo', $schedules); + $this->assertContains('Desayuno', $schedules); + $this->assertContains('Cena', $schedules); + } + + private function createFiestaFutbolInfantilTenant(): Tenant + { + $tenant = $this->createTenant('fiesta_futbol_infantil'); + $this->seed([AttributeSeeder::class, FiestaFutbolInfantilProductSeeder::class]); + + return $tenant->refresh(); + } + + private function createTenant(string $code): Tenant + { + $headerLogo = $this->createAttachment("{$code}-header.png"); + $footerLogo = $this->createAttachment("{$code}-footer.png"); + + return Tenant::query()->create([ + 'codigo' => $code, + 'nombre' => ucfirst($code), + 'dominio' => "{$code}.test", + 'primary_color' => '#111111', + 'secondary_color' => '#222222', + 'danger_color' => '#333333', + 'success_color' => '#444444', + 'header_bg_color' => '#ffffff', + 'footer_bg_color' => '#ffffff', + 'header_logo_id' => $headerLogo->id, + 'footer_logo_id' => $footerLogo->id, + ]); + } + + private function createAttachment(string $filename): Attachment + { + return Attachment::query()->create([ + 'path' => "tests/{$filename}", + 'filename' => $filename, + 'type' => AttachmentType::Image, + 'mime_type' => 'image/png', + ]); + } + + private function createAdminAppUser(Tenant $tenant): User + { + return User::factory()->create([ + 'rol_codigo' => RoleCode::AdminApp->value, + 'tenant_codigo' => $tenant->codigo, + ]); + } + + private function grantTicketsMenu(Tenant $tenant): void + { + $menu = Menu::query()->create([ + 'code' => 'adminapp.tickets', + 'label' => 'Tickets', + 'route' => '/admin/tickets', + ]); + + $tenant->menues()->attach($menu->code); + } +}