diff --git a/api/src/pcapi/core/offers/models.py b/api/src/pcapi/core/offers/models.py index 6e9d98d2237..d730b189492 100644 --- a/api/src/pcapi/core/offers/models.py +++ b/api/src/pcapi/core/offers/models.py @@ -833,13 +833,22 @@ def isBookable(self) -> bool: @hybrid_property def is_eligible_for_search(self) -> bool: - if self.futureOffer: - return self.isReleased and self.futureOffer.isWaitingForPublication + if self.futureOffer and not self.isActive: + offerer = self.venue.managingOfferer + return ( + offerer.isActive + and offerer.isValidated + and self.validation == OfferValidationStatus.APPROVED + and self.futureOffer.isWaitingForPublication + ) return self.is_released_and_bookable @is_eligible_for_search.expression # type: ignore[no-redef] def is_eligible_for_search(cls) -> BooleanClauseList: # pylint: disable=no-self-argument - return sa.and_(cls._released, sa.or_(Stock._bookable, FutureOffer.isWaitingForPublication)) + return sa.or_( + sa.and_(cls._released, Stock._bookable), + sa.and_(cls.validation == OfferValidationStatus.APPROVED, FutureOffer.isWaitingForPublication), + ) @hybrid_property def is_released_and_bookable(self) -> bool: diff --git a/api/src/pcapi/core/search/backends/algolia.py b/api/src/pcapi/core/search/backends/algolia.py index ba632e692f6..de996f694af 100644 --- a/api/src/pcapi/core/search/backends/algolia.py +++ b/api/src/pcapi/core/search/backends/algolia.py @@ -565,6 +565,7 @@ def serialize_offer(cls, offer: offers_models.Offer, last_30_days_bookings: int) "isEvent": offer.isEvent, "isForbiddenToUnderage": offer.is_forbidden_to_underage, "isPermanent": offer.isPermanent, + "isReleased": offer.isReleased if offer.publicationDate else None, "isThing": offer.isThing, "last30DaysBookings": last_30_days_bookings, "last30DaysBookingsRange": get_last_30_days_bookings_range(last_30_days_bookings), @@ -573,6 +574,7 @@ def serialize_offer(cls, offer: offers_models.Offer, last_30_days_bookings: int) "name": offer.name, "nativeCategoryId": offer.subcategory.native_category_id, "prices": sorted(prices), + "publicationDate": offer.publicationDate.isoformat() if offer.publicationDate else None, "rankingWeight": offer.rankingWeight, "releaseDate": release_date, "bookFormat": extra_data.get("bookFormat"), diff --git a/api/tests/core/offers/test_models.py b/api/tests/core/offers/test_models.py index 8acf247bdef..1ada2fe7907 100644 --- a/api/tests/core/offers/test_models.py +++ b/api/tests/core/offers/test_models.py @@ -747,18 +747,19 @@ def test_unicity_headline_offer_by_venue(self): class OfferIsSearchableTest: def test_offer_is_future(self): - offer_1 = factories.OfferFactory(isActive=True) - offer_2 = factories.OfferFactory(isActive=True) - offer_3 = factories.OfferFactory(isActive=False) + offer_1 = factories.OfferFactory(isActive=False) + offer_2 = factories.OfferFactory(isActive=False) + offer_3 = factories.OfferFactory(isActive=True) + factories.StockFactory(offer=offer_3) future_publication_date = datetime.datetime.utcnow() + datetime.timedelta(days=30) past_publication_date = datetime.datetime.utcnow() - datetime.timedelta(days=30) - _ = factories.FutureOfferFactory(offerId=offer_1.id, publicationDate=future_publication_date) - _ = factories.FutureOfferFactory(offerId=offer_2.id, publicationDate=past_publication_date) - _ = factories.FutureOfferFactory(offerId=offer_3.id, publicationDate=future_publication_date) + factories.FutureOfferFactory(offerId=offer_1.id, publicationDate=future_publication_date) + factories.FutureOfferFactory(offerId=offer_2.id, publicationDate=past_publication_date) + factories.FutureOfferFactory(offerId=offer_3.id, publicationDate=future_publication_date) assert offer_1.is_eligible_for_search assert not offer_2.is_eligible_for_search - assert not offer_3.is_eligible_for_search + assert offer_3.is_eligible_for_search # hybrid property: also check SQL expression results = ( @@ -768,13 +769,14 @@ def test_offer_is_future(self): .filter(models.Offer.is_eligible_for_search) .all() ) - assert len(results) == 1 + assert len(results) == 2 assert results[0].id == offer_1.id + assert results[1].id == offer_3.id def test_offer_is_bookable(self): offer_1 = factories.OfferFactory(isActive=True) offer_2 = factories.OfferFactory(isActive=False) - _ = factories.StockFactory(offer=offer_1) + factories.StockFactory(offer=offer_1) assert offer_1.is_eligible_for_search assert not offer_2.is_eligible_for_search diff --git a/api/tests/core/search/test_api.py b/api/tests/core/search/test_api.py index 250dd770dce..90128eea328 100644 --- a/api/tests/core/search/test_api.py +++ b/api/tests/core/search/test_api.py @@ -25,6 +25,13 @@ def make_bookable_offer() -> offers_models.Offer: return offers_factories.StockFactory().offer +def make_future_offer() -> offers_models.Offer: + offer = offers_factories.OfferFactory(isActive=False) + publication_date = datetime.datetime.utcnow() + datetime.timedelta(days=30) + offers_factories.FutureOfferFactory(offerId=offer.id, publicationDate=publication_date) + return offer + + def make_booked_offer() -> offers_models.Offer: offer = make_bookable_offer() offers_factories.StockFactory(offer=offer) @@ -126,12 +133,14 @@ def test_index_venues_in_queue(app): class ReindexOfferIdsTest: def test_index_new_offer(self): offer = make_bookable_offer() + future_offer = make_future_offer() assert search_testing.search_store["offers"] == {} - search.reindex_offer_ids([offer.id]) - assert offer.id in search_testing.search_store["offers"] + search.reindex_offer_ids([offer.id, future_offer.id]) + assert set(search_testing.search_store["offers"]) == {offer.id, future_offer.id} def test_no_unexpected_query_made(self): offer_ids = [make_bookable_offer().id for _ in range(3)] + offer_ids.extend([make_future_offer().id for _ in range(3, 6)]) with assert_no_duplicated_queries(): search.reindex_offer_ids(offer_ids) @@ -154,12 +163,13 @@ def test_that_base_query_is_correct(self, app): # `reindex_offer_ids()`. unbookable = make_unbookable_offer() bookable = make_bookable_offer() + future_offer = make_future_offer() past = datetime.datetime.utcnow() - datetime.timedelta(days=10) future = datetime.datetime.utcnow() + datetime.timedelta(days=10) multi_dates_unbookable = offers_factories.EventStockFactory(beginningDatetime=past).offer multi_dates_bookable = offers_factories.EventStockFactory(beginningDatetime=past).offer offers_factories.EventStockFactory(offer=multi_dates_bookable, beginningDatetime=future) - offer_ids = {unbookable.id, bookable.id, multi_dates_unbookable.id, multi_dates_bookable.id} + offer_ids = {unbookable.id, bookable.id, future_offer.id, multi_dates_unbookable.id, multi_dates_bookable.id} for offer_id in offer_ids: search_testing.search_store["offers"][offer_id] = "dummy" app.redis_client.hset(algolia.REDIS_HASHMAP_INDEXED_OFFERS_NAME, offer_id, "") @@ -167,7 +177,7 @@ def test_that_base_query_is_correct(self, app): with assert_no_duplicated_queries(): search.reindex_offer_ids(offer_ids) - assert set(search_testing.search_store["offers"]) == {bookable.id, multi_dates_bookable.id} + assert set(search_testing.search_store["offers"]) == {bookable.id, future_offer.id, multi_dates_bookable.id} @mock.patch("pcapi.core.search.backends.testing.FakeClient.save_objects", fail) @pytest.mark.settings(CATCH_INDEXATION_EXCEPTIONS=True) # as on prod: don't raise errors diff --git a/api/tests/core/search/test_serialize_algolia.py b/api/tests/core/search/test_serialize_algolia.py index 3a74f896041..2d20184818e 100644 --- a/api/tests/core/search/test_serialize_algolia.py +++ b/api/tests/core/search/test_serialize_algolia.py @@ -483,6 +483,22 @@ def test_serialize_venue_with_one_bookable_offer(): assert serialized["has_at_least_one_bookable_offer"] +def test_serialize_future_offer(): + offer_1 = offers_factories.OfferFactory(isActive=False) + offer_2 = offers_factories.OfferFactory(isActive=True) + publication_date = datetime.datetime.utcnow() + datetime.timedelta(days=30) + offers_factories.FutureOfferFactory(offerId=offer_1.id, publicationDate=publication_date) + offers_factories.FutureOfferFactory(offerId=offer_2.id, publicationDate=publication_date) + + serialized = algolia.AlgoliaBackend().serialize_offer(offer_1, 0) + assert serialized["offer"]["publicationDate"] == publication_date.isoformat() + assert serialized["offer"]["isReleased"] == False + + serialized = algolia.AlgoliaBackend().serialize_offer(offer_2, 0) + assert serialized["offer"]["publicationDate"] == publication_date.isoformat() + assert serialized["offer"]["isReleased"] == True + + def test_serialize_collective_offer_template(): domain1 = educational_factories.EducationalDomainFactory(name="Danse") domain2 = educational_factories.EducationalDomainFactory(name="Architecture")