feat(events): add event categories and filtering
This commit is contained in:
@@ -37,7 +37,8 @@ class TenantBootstrapService
|
||||
'roles',
|
||||
fn ($query) => $query->where('codigo', RoleCode::User->value)
|
||||
),
|
||||
'categories' => fn ($query) => $query->orderBy('nombre'),
|
||||
$tenant->storefront_website_type_code === 'onticket_multi_event'
|
||||
? 'eventCategories' : 'categories' => fn ($query) => $query->orderBy('nombre'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Domains\Core\Client\Models\Client;
|
||||
use App\Domains\Ticketing\Event\Models\EventDate;
|
||||
use App\Domains\Ticketing\Event\Models\EventDateChange;
|
||||
use App\Domains\Ticketing\Event\Models\Event;
|
||||
use App\Domains\Ticketing\Event\Models\EventCategory;
|
||||
use App\Domains\Core\Menu\Models\Menu;
|
||||
use App\Domains\Core\Menu\Models\TenantMenu;
|
||||
use App\Domains\Core\Tenant\Enums\CartEditingPolicy;
|
||||
@@ -265,6 +266,12 @@ class Tenant extends Model
|
||||
return $this->hasMany(Category::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return HasMany<EventCategory, $this> */
|
||||
public function eventCategories(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventCategory::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return HasMany<ScanAttempt, $this> */
|
||||
public function scanAttempts(): HasMany
|
||||
{
|
||||
|
||||
@@ -99,10 +99,14 @@ class TenantResource extends JsonResource
|
||||
'menues',
|
||||
fn () => $this->menuTree($this->menues)
|
||||
),
|
||||
'categories' => $this->whenLoaded(
|
||||
'categories',
|
||||
fn () => $this->categoryTree($this->categories)
|
||||
),
|
||||
'categories' => $this->storefront_website_type_code === 'onticket_multi_event'
|
||||
? $this->whenLoaded('eventCategories', fn () => $this->eventCategories
|
||||
->map(fn ($category) => [
|
||||
'id' => $category->id,
|
||||
'nombre' => $category->nombre,
|
||||
'subcategories' => [],
|
||||
])->values())
|
||||
: $this->whenLoaded('categories', fn () => $this->categoryTree($this->categories)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,12 @@ class PublicEventController extends Controller
|
||||
abort_unless($tenant->storefront_website_type_code === 'onticket_multi_event', 404);
|
||||
|
||||
$term = trim((string) $request->validated('q', ''));
|
||||
return PublicEventResource::collection($events->list($tenant, $term, (int) $request->validated('page', 1)));
|
||||
return PublicEventResource::collection($events->list(
|
||||
$tenant,
|
||||
$term,
|
||||
(int) $request->validated('page', 1),
|
||||
$request->validated('category_id', null) === null ? null : (int) $request->validated('category_id'),
|
||||
));
|
||||
}
|
||||
|
||||
public function show(Tenant $tenant, Event $event, PublicEventService $events): PublicEventResource
|
||||
|
||||
@@ -13,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable(['tenant_code', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id'])]
|
||||
#[Fillable(['tenant_code', 'event_category_id', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id'])]
|
||||
class Event extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
@@ -29,6 +29,12 @@ class Event extends Model
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<EventCategory, $this> */
|
||||
public function eventCategory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EventCategory::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Attachment, $this> */
|
||||
public function attachment(): BelongsTo
|
||||
{
|
||||
|
||||
25
app/Domains/Ticketing/Event/Models/EventCategory.php
Normal file
25
app/Domains/Ticketing/Event/Models/EventCategory.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Ticketing\Event\Models;
|
||||
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable(['tenant_code', 'nombre'])]
|
||||
class EventCategory extends Model
|
||||
{
|
||||
/** @return BelongsTo<Tenant, $this> */
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return HasMany<Event, $this> */
|
||||
public function events(): HasMany
|
||||
{
|
||||
return $this->hasMany(Event::class);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ class ListPublicEventsRequest extends FormRequest
|
||||
return [
|
||||
'q' => ['sometimes', 'string', 'max:120'],
|
||||
'page' => ['sometimes', 'integer', 'min:1'],
|
||||
'category_id' => ['sometimes', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class PublicEventResource extends JsonResource
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_category_id' => $this->event_category_id,
|
||||
'title' => $this->title,
|
||||
'subtitle' => $this->subtitle,
|
||||
'description' => $this->description,
|
||||
|
||||
@@ -8,12 +8,17 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class PublicEventService
|
||||
{
|
||||
public function list(Tenant $tenant, string $term, int $page): LengthAwarePaginator
|
||||
public function list(Tenant $tenant, string $term, int $page, ?int $categoryId = null): LengthAwarePaginator
|
||||
{
|
||||
$query = $tenant->events()
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
|
||||
if ($categoryId !== null) {
|
||||
abort_unless($tenant->eventCategories()->whereKey($categoryId)->exists(), 404);
|
||||
$query->where('event_category_id', $categoryId);
|
||||
}
|
||||
|
||||
if ($term !== '') {
|
||||
$query->where(function ($query) use ($term): void {
|
||||
$query->where('title', 'like', '%'.$term.'%')
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('event_categories', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('nombre');
|
||||
$table->timestamps();
|
||||
$table->foreign('tenant_code')->references('codigo')->on('tenants')->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->unique(['tenant_code', 'nombre']);
|
||||
});
|
||||
|
||||
Schema::table('events', function (Blueprint $table): void {
|
||||
$table->foreignId('event_category_id')->nullable()->constrained('event_categories')->nullOnDelete();
|
||||
});
|
||||
|
||||
if (DB::table('tenants')->where('codigo', 'onticket')->exists()) {
|
||||
$now = now();
|
||||
foreach (['Música', 'Teatro', 'Deportes', 'Infantiles', 'Otros'] as $nombre) {
|
||||
DB::table('event_categories')->insert([
|
||||
'tenant_code' => 'onticket', 'nombre' => $nombre,
|
||||
'created_at' => $now, 'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('events', fn (Blueprint $table) => $table->dropConstrainedForeignId('event_category_id'));
|
||||
Schema::dropIfExists('event_categories');
|
||||
}
|
||||
};
|
||||
@@ -113,6 +113,10 @@ class TenantSeeder extends Seeder
|
||||
|
||||
$onTicketTenant = Tenant::query()->where('codigo', 'onticket')->firstOrFail();
|
||||
|
||||
foreach (['Música', 'Teatro', 'Deportes', 'Infantiles', 'Otros'] as $nombre) {
|
||||
$onTicketTenant->eventCategories()->firstOrCreate(['nombre' => $nombre]);
|
||||
}
|
||||
|
||||
if ($onTicketTenant->storefront_website_type_code === 'onticket_multi_event' && ! $onTicketTenant->display_seach_bar) {
|
||||
$onTicketTenant->update(['display_seach_bar' => true]);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class TestEventsSeeder extends Seeder
|
||||
$events = [
|
||||
[
|
||||
'title' => '[Prueba] Noche de Música en Vivo',
|
||||
'category' => 'Música',
|
||||
'subtitle' => 'Una noche para cantar y bailar',
|
||||
'description' => 'Evento de prueba para explorar la cartelera de OnTicket.',
|
||||
'location' => 'Teatro Broadway, Rosario',
|
||||
@@ -30,6 +31,7 @@ class TestEventsSeeder extends Seeder
|
||||
],
|
||||
[
|
||||
'title' => '[Prueba] Festival de Sabores',
|
||||
'category' => 'Otros',
|
||||
'subtitle' => 'Gastronomía y música para toda la familia',
|
||||
'description' => 'Evento de prueba con dos jornadas disponibles.',
|
||||
'location' => 'Parque de España, Rosario',
|
||||
@@ -41,6 +43,7 @@ class TestEventsSeeder extends Seeder
|
||||
],
|
||||
[
|
||||
'title' => '[Prueba] Stand Up en el Centro',
|
||||
'category' => 'Teatro',
|
||||
'subtitle' => 'Humor para compartir',
|
||||
'description' => 'Evento de prueba para visualizar distintas propuestas.',
|
||||
'location' => 'Centro Cultural La Comedia, Rosario',
|
||||
@@ -60,9 +63,16 @@ class TestEventsSeeder extends Seeder
|
||||
'description' => $definition['description'],
|
||||
'location' => $definition['location'],
|
||||
'published_at' => now(),
|
||||
'event_category_id' => $tenant->eventCategories()
|
||||
->where('nombre', $definition['category'])->value('id'),
|
||||
],
|
||||
);
|
||||
|
||||
if ($event->event_category_id === null) {
|
||||
$event->update(['event_category_id' => $tenant->eventCategories()
|
||||
->where('nombre', $definition['category'])->value('id')]);
|
||||
}
|
||||
|
||||
if ($event->attachment_id === null) {
|
||||
$path = public_path('images/tennants/onticket/test-events/'.$definition['image']);
|
||||
|
||||
|
||||
@@ -23,6 +23,22 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_multi_event_bootstrap_returns_event_categories_instead_of_catalog_categories(): void
|
||||
{
|
||||
$this->seed(WebsiteTypeSeeder::class);
|
||||
$tenant = $this->createTenant();
|
||||
$tenant->update(['storefront_website_type_code' => 'onticket_multi_event']);
|
||||
$tenant->categories()->create(['nombre' => 'Producto']);
|
||||
$eventCategory = $tenant->eventCategories()->create(['nombre' => 'Música']);
|
||||
|
||||
$this->getJson('/api/tenants/bootstrap?dominio=acme.com&path=%2F')
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data.categories')
|
||||
->assertJsonPath('data.categories.0.id', $eventCategory->id)
|
||||
->assertJsonPath('data.categories.0.nombre', 'Música')
|
||||
->assertJsonPath('data.categories.0.subcategories', []);
|
||||
}
|
||||
|
||||
public function test_it_bootstraps_a_tenant_by_domain(): void
|
||||
{
|
||||
$hdrKey = (string) Str::uuid();
|
||||
|
||||
Reference in New Issue
Block a user