diff --git a/app/Domains/Catalog/Models/CatalogItem.php b/app/Domains/Catalog/Models/CatalogItem.php index 74e3b7b..18309a0 100644 --- a/app/Domains/Catalog/Models/CatalogItem.php +++ b/app/Domains/Catalog/Models/CatalogItem.php @@ -221,11 +221,12 @@ class CatalogItem extends Model public function visibleVariants(?int $includedVariantId = null): Collection { return $this->variants - ->filter(fn (Variant $variant): bool => ($includedVariantId !== null && $variant->id === $includedVariantId) - || ($variant->isSellable() && ( - $this->inventory_policy === InventoryPolicy::Unlimited - || ($variant->inventory?->availableStock() ?? 0) > 0 - ))) + ->filter(fn (Variant $variant): bool => $variant->hasOnlyActiveEventDates() + && (($includedVariantId !== null && $variant->id === $includedVariantId) + || ($variant->isSellable() && ( + $this->inventory_policy === InventoryPolicy::Unlimited + || ($variant->inventory?->availableStock() ?? 0) > 0 + )))) ->values(); } diff --git a/app/Domains/Catalog/Models/Variant.php b/app/Domains/Catalog/Models/Variant.php index 522b781..0873958 100644 --- a/app/Domains/Catalog/Models/Variant.php +++ b/app/Domains/Catalog/Models/Variant.php @@ -95,7 +95,22 @@ class Variant extends Model public function isSellable(): bool { return $this->sales_disabled_at === null - && $this->replaced_by_variant_id === null; + && $this->replaced_by_variant_id === null + && $this->hasOnlyActiveEventDates(); + } + + public function hasOnlyActiveEventDates(): bool + { + if (! $this->exists + && $this->event_date_id === null + && ! $this->relationLoaded('eventDates')) { + return true; + } + + return $this->selectedEventDates()->every( + fn (EventDate $eventDate): bool => $eventDate->rescheduled_to_event_date_id === null + && $eventDate->suspended_at === null, + ); } /** @return HasMany */ diff --git a/tests/Unit/Catalog/CatalogModelsTest.php b/tests/Unit/Catalog/CatalogModelsTest.php index e989b2c..45ca556 100644 --- a/tests/Unit/Catalog/CatalogModelsTest.php +++ b/tests/Unit/Catalog/CatalogModelsTest.php @@ -346,6 +346,34 @@ class CatalogModelsTest extends TestCase $this->assertSame([$unavailable, $available], $item->visibleVariants()->all()); } + public function test_catalog_item_never_exposes_variants_with_inactive_event_dates(): void + { + $activeDate = new EventDate(['date' => '2026-10-10']); + $rescheduledDate = new EventDate([ + 'date' => '2026-09-08', + 'rescheduled_to_event_date_id' => 100, + ]); + $suspendedDate = new EventDate([ + 'date' => '2026-10-08', + 'suspended_at' => now(), + ]); + + $active = (new Variant)->setRelation('eventDates', new EloquentCollection([$activeDate])); + $rescheduled = (new Variant)->setRelation('eventDates', new EloquentCollection([$rescheduledDate])); + $suspended = (new Variant)->setRelation('eventDates', new EloquentCollection([$suspendedDate])); + $active->id = 10; + $rescheduled->id = 20; + $suspended->id = 30; + + $item = new CatalogItem; + $item->inventory_policy = InventoryPolicy::Unlimited; + $item->setRelation('variants', new EloquentCollection([$active, $rescheduled, $suspended])); + + $this->assertSame([$active], $item->visibleVariants()->all()); + $this->assertSame([$active], $item->visibleVariants($rescheduled->id)->all()); + $this->assertSame([$active], $item->visibleVariants($suspended->id)->all()); + } + public function test_catalog_item_prioritizes_its_inventory_over_variants(): void { $item = new CatalogItem;