diff --git a/app/Domains/Commerce/Purchase/Models/Purchase.php b/app/Domains/Commerce/Purchase/Models/Purchase.php index 5c75fa7f..68fec2f3 100644 --- a/app/Domains/Commerce/Purchase/Models/Purchase.php +++ b/app/Domains/Commerce/Purchase/Models/Purchase.php @@ -2,13 +2,14 @@ namespace App\Domains\Commerce\Purchase\Models; -use App\Domains\Core\Auth\Models\User; use App\Domains\Commerce\Cart\Models\Cart; use App\Domains\Commerce\Catalog\Models\StockReservation; -use App\Shared\Logging\Models\Concerns\LogsValueChanges; use App\Domains\Commerce\Purchase\Events\PurchasePaid; +use App\Domains\Core\Auth\Models\User; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Event\Models\Event; use App\Domains\Ticketing\Ticket\Models\Ticket; +use App\Shared\Logging\Models\Concerns\LogsValueChanges; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -21,6 +22,7 @@ use Illuminate\Support\Facades\DB; #[Fillable([ 'cart_id', 'stock_reservation_id', + 'event_id', 'tenant_codigo', 'user_id', 'status', @@ -149,6 +151,7 @@ class Purchase extends Model return [ 'cart_id' => 'integer', 'stock_reservation_id' => 'integer', + 'event_id' => 'integer', 'user_id' => 'integer', 'total' => 'decimal:2', ]; @@ -162,6 +165,12 @@ class Purchase extends Model return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo'); } + /** @return BelongsTo */ + public function event(): BelongsTo + { + return $this->belongsTo(Event::class); + } + /** * @return BelongsTo */ diff --git a/app/Domains/Commerce/Purchase/Resources/PurchaseResource.php b/app/Domains/Commerce/Purchase/Resources/PurchaseResource.php index 2e98a2e8..d5624cf5 100644 --- a/app/Domains/Commerce/Purchase/Resources/PurchaseResource.php +++ b/app/Domains/Commerce/Purchase/Resources/PurchaseResource.php @@ -36,11 +36,11 @@ class PurchaseResource extends JsonResource === StorefrontWebsiteType::CHECKOUT_SUMMARY_EVENT; /** @var Event|null $checkoutEvent */ $checkoutEvent = $usesEventSummary - ? $items + ? ($this->event ?? $items ->map(fn (PurchaseItem $item) => $item->sourceCatalogItem?->event) ->filter() ->unique('id') - ->first() + ->first()) : null; $subtotal = $items->isNotEmpty() @@ -62,6 +62,7 @@ class PurchaseResource extends JsonResource return [ 'id' => $this->id, 'cart_id' => $this->cart_id, + 'event_id' => $this->event_id, 'tenant_codigo' => $this->tenant_codigo, 'user_id' => $this->user_id, 'created_at' => $this->created_at, diff --git a/app/Domains/Commerce/Purchase/Services/Checkout/PurchaseResponseLoader.php b/app/Domains/Commerce/Purchase/Services/Checkout/PurchaseResponseLoader.php index ed82d2d6..88dfb5f3 100644 --- a/app/Domains/Commerce/Purchase/Services/Checkout/PurchaseResponseLoader.php +++ b/app/Domains/Commerce/Purchase/Services/Checkout/PurchaseResponseLoader.php @@ -14,6 +14,7 @@ class PurchaseResponseLoader === StorefrontWebsiteType::CHECKOUT_SUMMARY_EVENT; $relations = [ 'tenant', + 'event.attachment', 'items.sourceCatalogItem.event.attachment', 'stockReservation', ]; diff --git a/app/Domains/Commerce/Purchase/Services/Checkout/StartCheckoutService.php b/app/Domains/Commerce/Purchase/Services/Checkout/StartCheckoutService.php index e2a1bf14..e21ab889 100644 --- a/app/Domains/Commerce/Purchase/Services/Checkout/StartCheckoutService.php +++ b/app/Domains/Commerce/Purchase/Services/Checkout/StartCheckoutService.php @@ -206,6 +206,7 @@ class StartCheckoutService fn (array $line): float => $line['selection']->getPrice() * $line['quantity'], ), $cart->getKey(), + $cartItems->first()?->catalogItem?->event_id, ); $cart->update(['current_purchase_id' => $purchase->getKey()]); $this->reservations->attachToPurchase($cart, $purchase, $this->checkoutExpiration()); @@ -273,6 +274,7 @@ class StartCheckoutService $purchaseData, $cart->getTotalAmount(), $cart->getKey(), + $cartItems->first()?->catalogItem?->event_id, ); $cart->update(['current_purchase_id' => $purchase->getKey()]); $this->reservations->attachToPurchase($cart, $purchase, $this->checkoutExpiration()); @@ -410,10 +412,12 @@ class StartCheckoutService array $purchaseData, float $total, ?int $cartId, + ?int $eventId, ): Purchase { return Purchase::query()->create([ ...$purchaseData, 'cart_id' => $cartId, + 'event_id' => $eventId, 'tenant_codigo' => $tenant->codigo, 'user_id' => $userId, 'status' => Purchase::STATUS_CREATED, diff --git a/app/Domains/Core/Client/Models/Client.php b/app/Domains/Core/Client/Models/Client.php index f52054ff..e7381320 100644 --- a/app/Domains/Core/Client/Models/Client.php +++ b/app/Domains/Core/Client/Models/Client.php @@ -2,8 +2,9 @@ namespace App\Domains\Core\Client\Models; -use App\Shared\Integration\Models\ClientIntegration; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Event\Models\Event; +use App\Shared\Integration\Models\ClientIntegration; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -22,6 +23,12 @@ class Client extends Model return $this->hasMany(Tenant::class); } + /** @return HasMany */ + public function events(): HasMany + { + return $this->hasMany(Event::class); + } + /** @return HasMany */ public function integrations(): HasMany { diff --git a/app/Domains/Ticketing/Event/Models/Event.php b/app/Domains/Ticketing/Event/Models/Event.php index d27905f1..ca3e042d 100644 --- a/app/Domains/Ticketing/Event/Models/Event.php +++ b/app/Domains/Ticketing/Event/Models/Event.php @@ -3,6 +3,7 @@ namespace App\Domains\Ticketing\Event\Models; use App\Domains\Commerce\Catalog\Models\CatalogItem; +use App\Domains\Core\Client\Models\Client; use App\Domains\Core\Tenant\Models\SocialMedia; use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Ticketing\Ticket\Models\Ticket; @@ -14,14 +15,14 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; -#[Fillable(['tenant_code', 'slug', 'event_category_id', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id'])] +#[Fillable(['client_id', 'tenant_code', 'slug', 'event_category_id', 'title', 'subtitle', 'description', 'location', 'exact_location', 'date_text', 'published_at', 'attachment_id'])] class Event extends Model { use HasFactory; protected function casts(): array { - return ['published_at' => 'datetime', 'exact_location' => 'array']; + return ['client_id' => 'integer', 'published_at' => 'datetime', 'exact_location' => 'array']; } /** @return BelongsTo */ @@ -30,6 +31,17 @@ class Event extends Model return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo'); } + /** @return BelongsTo */ + public function client(): BelongsTo + { + return $this->belongsTo(Client::class); + } + + public function effectiveClient(): Client + { + return $this->client ?? $this->tenant->client; + } + /** @return BelongsTo */ public function eventCategory(): BelongsTo { diff --git a/database/migrations/2026_09_28_000000_add_client_id_to_events.php b/database/migrations/2026_09_28_000000_add_client_id_to_events.php new file mode 100644 index 00000000..039f6804 --- /dev/null +++ b/database/migrations/2026_09_28_000000_add_client_id_to_events.php @@ -0,0 +1,26 @@ +foreignId('client_id') + ->nullable() + ->after('id') + ->constrained('clients') + ->restrictOnDelete(); + }); + } + + public function down(): void + { + Schema::table('events', function (Blueprint $table): void { + $table->dropConstrainedForeignId('client_id'); + }); + } +}; diff --git a/database/migrations/2026_09_28_000100_add_event_id_to_compras.php b/database/migrations/2026_09_28_000100_add_event_id_to_compras.php new file mode 100644 index 00000000..0dad3a5b --- /dev/null +++ b/database/migrations/2026_09_28_000100_add_event_id_to_compras.php @@ -0,0 +1,44 @@ +foreignId('event_id') + ->nullable() + ->after('tenant_codigo') + ->constrained('events') + ->nullOnDelete(); + }); + + DB::table('compras') + ->orderBy('id') + ->each(function (object $purchase): void { + $eventIds = DB::table('compra_items') + ->join('catalog_items', 'catalog_items.id', '=', 'compra_items.source_catalog_item_id') + ->where('compra_items.compra_id', $purchase->id) + ->whereNotNull('catalog_items.event_id') + ->distinct() + ->pluck('catalog_items.event_id'); + + if ($eventIds->count() === 1) { + DB::table('compras') + ->where('id', $purchase->id) + ->update(['event_id' => $eventIds->first()]); + } + }); + } + + public function down(): void + { + Schema::table('compras', function (Blueprint $table): void { + $table->dropConstrainedForeignId('event_id'); + }); + } +}; diff --git a/database/migrations/2026_09_28_000200_assign_arrufo_event_to_pyme_rural_client.php b/database/migrations/2026_09_28_000200_assign_arrufo_event_to_pyme_rural_client.php new file mode 100644 index 00000000..601f22e8 --- /dev/null +++ b/database/migrations/2026_09_28_000200_assign_arrufo_event_to_pyme_rural_client.php @@ -0,0 +1,42 @@ +where('code', 'pyme_rural') + ->value('id'); + + if ($clientId === null) { + return; + } + + DB::table('events') + ->where('tenant_code', 'onticket') + ->where('title', self::EVENT_TITLE) + ->update(['client_id' => $clientId]); + } + + public function down(): void + { + $clientId = DB::table('clients') + ->where('code', 'pyme_rural') + ->value('id'); + + if ($clientId === null) { + return; + } + + DB::table('events') + ->where('tenant_code', 'onticket') + ->where('title', self::EVENT_TITLE) + ->where('client_id', $clientId) + ->update(['client_id' => null]); + } +}; diff --git a/database/seeders/FiestaTradicionArrufoSeeder.php b/database/seeders/FiestaTradicionArrufoSeeder.php index 79b0dbbd..69263cb7 100644 --- a/database/seeders/FiestaTradicionArrufoSeeder.php +++ b/database/seeders/FiestaTradicionArrufoSeeder.php @@ -6,6 +6,7 @@ use App\Domains\Commerce\Catalog\Enums\InventoryPolicy; use App\Domains\Commerce\Catalog\Models\Attribute; use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Commerce\Catalog\Services\CatalogService; +use App\Domains\Core\Client\Models\Client; use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Ticketing\Event\Models\Event; use App\Shared\Attachable\Services\AttachmentService; @@ -35,6 +36,12 @@ class FiestaTradicionArrufoSeeder extends Seeder throw new RuntimeException("Tenant 'onticket' no encontrado."); } + $client = Client::query()->where('code', 'pyme_rural')->first(); + + if ($client === null) { + throw new RuntimeException("Client 'pyme_rural' no encontrado."); + } + Attribute::query()->firstOrCreate( ['tenant_codigo' => $tenant->codigo, 'codigo' => 'event_date'], ['nombre' => 'Fecha', 'type' => FieldType::EventDate, 'is_required' => true], @@ -45,6 +52,7 @@ class FiestaTradicionArrufoSeeder extends Seeder ['published_at' => now()], ); $event->update([ + 'client_id' => $client->id, 'subtitle' => 'Una noche para celebrar nuestras raíces y mantener viva la tradición gaucha.', 'description' => 'La 26.º Fiesta de la Tradición y 4.º Encuentro de Agrupaciones Gauchas reunirá a agrupaciones, artesanos, pilcheros y público en general para compartir una jornada dedicada a nuestras costumbres y cultura.' ."\n\n".'Un encuentro para disfrutar de la tradición, la identidad gaucha y el espíritu de camaradería, en el Predio de Doma del Club Unión Deportiva Arrufó.' diff --git a/tests/Feature/Migrations/AddClientIdToEventsTest.php b/tests/Feature/Migrations/AddClientIdToEventsTest.php new file mode 100644 index 00000000..1fdb6e0d --- /dev/null +++ b/tests/Feature/Migrations/AddClientIdToEventsTest.php @@ -0,0 +1,57 @@ +id(); + $table->string('code'); + $table->string('name'); + }); + Schema::create('tenants', function (Blueprint $table): void { + $table->id(); + $table->foreignId('client_id'); + $table->string('codigo'); + }); + Schema::create('events', function (Blueprint $table): void { + $table->id(); + $table->string('tenant_code'); + $table->string('title'); + $table->timestamps(); + }); + + DB::table('clients')->insert([ + ['id' => 10, 'code' => 'tenant-client', 'name' => 'Tenant Client'], + ['id' => 11, 'code' => 'event-client', 'name' => 'Event Client'], + ]); + DB::table('tenants')->insert(['id' => 20, 'client_id' => 10, 'codigo' => 'tenant']); + DB::table('events')->insert([ + 'id' => 30, + 'tenant_code' => 'tenant', + 'title' => 'Event', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $migration = require database_path('migrations/2026_09_28_000000_add_client_id_to_events.php'); + $migration->up(); + + $event = Event::query()->findOrFail(30); + $this->assertSame(10, $event->effectiveClient()->id); + + $event->update(['client_id' => 11]); + + $this->assertSame(11, $event->fresh()->effectiveClient()->id); + $this->assertTrue(Client::query()->findOrFail(11)->events()->whereKey(30)->exists()); + } +} diff --git a/tests/Feature/Migrations/AddEventIdToPurchasesTest.php b/tests/Feature/Migrations/AddEventIdToPurchasesTest.php new file mode 100644 index 00000000..2c6d24e3 --- /dev/null +++ b/tests/Feature/Migrations/AddEventIdToPurchasesTest.php @@ -0,0 +1,44 @@ + $table->id()); + Schema::create('compras', fn (Blueprint $table) => $table->id()); + Schema::create('catalog_items', function (Blueprint $table): void { + $table->id(); + $table->foreignId('event_id')->nullable(); + }); + Schema::create('compra_items', function (Blueprint $table): void { + $table->id(); + $table->foreignId('compra_id'); + $table->foreignId('source_catalog_item_id'); + }); + + DB::table('events')->insert([['id' => 10], ['id' => 11]]); + DB::table('compras')->insert([['id' => 20], ['id' => 21]]); + DB::table('catalog_items')->insert([ + ['id' => 30, 'event_id' => 10], + ['id' => 31, 'event_id' => 11], + ]); + DB::table('compra_items')->insert([ + ['compra_id' => 20, 'source_catalog_item_id' => 30], + ['compra_id' => 21, 'source_catalog_item_id' => 30], + ['compra_id' => 21, 'source_catalog_item_id' => 31], + ]); + + $migration = require database_path('migrations/2026_09_28_000100_add_event_id_to_compras.php'); + $migration->up(); + + $this->assertDatabaseHas('compras', ['id' => 20, 'event_id' => 10]); + $this->assertDatabaseHas('compras', ['id' => 21, 'event_id' => null]); + } +} diff --git a/tests/Feature/Migrations/AssignArrufoEventToPymeRuralClientTest.php b/tests/Feature/Migrations/AssignArrufoEventToPymeRuralClientTest.php new file mode 100644 index 00000000..91a80fd2 --- /dev/null +++ b/tests/Feature/Migrations/AssignArrufoEventToPymeRuralClientTest.php @@ -0,0 +1,50 @@ +id(); + $table->string('code'); + }); + Schema::create('events', function (Blueprint $table): void { + $table->id(); + $table->foreignId('client_id')->nullable(); + $table->string('tenant_code'); + $table->string('title'); + }); + + DB::table('clients')->insert([ + ['id' => 10, 'code' => 'pyme_rural'], + ['id' => 11, 'code' => 'onticket'], + ]); + DB::table('events')->insert([ + [ + 'id' => 20, + 'client_id' => null, + 'tenant_code' => 'onticket', + 'title' => '26.º Fiesta de la Tradición y 4.º Encuentro de Agrupaciones Gauchas', + ], + [ + 'id' => 21, + 'client_id' => 11, + 'tenant_code' => 'onticket', + 'title' => 'Otro evento', + ], + ]); + + $migration = require database_path('migrations/2026_09_28_000200_assign_arrufo_event_to_pyme_rural_client.php'); + $migration->up(); + + $this->assertDatabaseHas('events', ['id' => 20, 'client_id' => 10]); + $this->assertDatabaseHas('events', ['id' => 21, 'client_id' => 11]); + } +}