Skip to content

Commit

Permalink
(PC-33423)[API] feat: add command to update inactive headline offer u…
Browse files Browse the repository at this point in the history
…pper timespan
  • Loading branch information
ogeber-pass committed Jan 6, 2025
1 parent d9870eb commit 423ca9f
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
8 changes: 8 additions & 0 deletions api/src/pcapi/core/offers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,14 @@ def activate_future_offers(publication_date: datetime.datetime | None = None) ->
batch_update_offers(query, {"isActive": True})


def set_upper_timespan_of_inactive_headline_offers() -> None:
inactive_headline_offers = offers_repository.get_inactive_headline_offers()
for headline_offer in inactive_headline_offers:
headline_offer.timespan = db_utils.make_timerange(headline_offer.timespan.lower, datetime.datetime.utcnow())

db.session.commit()


def make_offer_headline(offer: models.Offer) -> models.HeadlineOffer:
if offer.status != OfferStatus.ACTIVE:
raise exceptions.InactiveOfferCanNotBeHeadline()
Expand Down
6 changes: 6 additions & 0 deletions api/src/pcapi/core/offers/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
@log_cron_with_transaction
def activate_future_offers() -> None:
offers_api.activate_future_offers()


@blueprint.cli.command("set_upper_timespan_of_inactive_headline_offers")
@log_cron_with_transaction
def set_upper_timespan_of_inactive_headline_offers() -> None:
offers_api.set_upper_timespan_of_inactive_headline_offers()
26 changes: 26 additions & 0 deletions api/src/pcapi/core/offers/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,32 @@ def get_offer_reaction_count_subquery() -> sa.sql.selectable.ScalarSelect:
)


def get_active_headline_offer(offer_id: int) -> models.HeadlineOffer | None:
return (
models.HeadlineOffer.query.join(models.Offer)
.filter(
models.HeadlineOffer.offerId == offer_id,
models.HeadlineOffer.isActive == True,
)
.one_or_none()
)


def get_inactive_headline_offers() -> list[models.HeadlineOffer]:
return (
models.HeadlineOffer.query.join(models.Offer, models.HeadlineOffer.offerId == models.Offer.id)
.filter(models.Offer.status != offer_mixin.OfferStatus.ACTIVE)
.filter(
# We don't want to fetch HeadlineOffers that have already been marked as finished
sa.or_(
sa.func.upper(models.HeadlineOffer.timespan).is_(None),
sa.func.upper(models.HeadlineOffer.timespan) <= datetime.datetime.utcnow(),
),
)
.all()
)


def get_product_reaction_count_subquery() -> sa.sql.selectable.ScalarSelect:
return (
sa.select(sa.func.count(reactions_models.Reaction.id))
Expand Down
43 changes: 43 additions & 0 deletions api/tests/core/offers/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2240,6 +2240,49 @@ def test_headline_offer_on_rejected_offer_is_inactive(self):
offer.validation = models.OfferValidationStatus.REJECTED
assert not offer.is_headline_offer

def test_set_upper_timespan_of_inactive_headline_offers(self):
venue_1 = offerers_factories.VenueFactory()
offer_1 = factories.OfferFactory(isActive=True, venue=venue_1)
factories.StockFactory(offer=offer_1)
venue_2 = offerers_factories.VenueFactory()
stock = factories.StockFactory(quantity=1)
offer_2 = factories.OfferFactory(isActive=True, venue=venue_2, stocks=[stock])
venue_3 = offerers_factories.VenueFactory()
offer_3 = factories.OfferFactory(isActive=True, venue=venue_3)
factories.StockFactory(offer=offer_3)

headline_offer_1 = factories.HeadlineOfferFactory(offer=offer_1)
headline_offer_2 = factories.HeadlineOfferFactory(offer=offer_2)
headline_offer_3 = factories.HeadlineOfferFactory(offer=offer_3)

assert headline_offer_1.isActive
assert headline_offer_1.timespan.upper is None
assert headline_offer_2.isActive
assert headline_offer_2.timespan.upper is None

offer_1.validation = models.OfferValidationStatus.REJECTED
stock.quantity = 0

api.set_upper_timespan_of_inactive_headline_offers()
assert not headline_offer_1.isActive
assert not headline_offer_1.timespan.upper is None
assert not headline_offer_2.isActive
assert not headline_offer_2.timespan.upper is None

assert headline_offer_3.isActive
assert headline_offer_3.timespan.upper is None

def test_do_not_update_upper_timespan_of_already_inactive_headline_offers(self):
offer = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=offer)
creation_time = datetime.utcnow() - timedelta(days=20)
finished_timespan = (creation_time, creation_time + timedelta(days=10))
old_headline_offer = factories.HeadlineOfferFactory(offer=offer, timespan=finished_timespan)
api.set_upper_timespan_of_inactive_headline_offers()
assert old_headline_offer.timespan.lower.date() == creation_time.date()
assert old_headline_offer.timespan.upper.date() == (creation_time + timedelta(days=10)).date()


@pytest.mark.usefixtures("db_session")
class OfferExpenseDomainsTest:
def test_offer_expense_domains(self):
Expand Down
80 changes: 80 additions & 0 deletions api/tests/core/offers/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -2588,3 +2588,83 @@ def test_should_return_no_result(self):

# Then
assert len(price_categories.all()) == 0


@pytest.mark.usefixtures("db_session")
class GetHeadlineOfferFiltersTest:
def test_get_headline_offer_basic(self):
offer = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=offer)
headline_offer = factories.HeadlineOfferFactory(offer=offer)

headline_offer_query_result = repository.get_active_headline_offer(offer.id)
assert headline_offer_query_result == headline_offer

def test_get_only_active_headline_offer(self):
offer = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=offer)
creation_time = datetime.datetime.utcnow() - datetime.timedelta(days=20)
finished_timespan = (creation_time, creation_time + datetime.timedelta(days=10))
headline_offer = factories.HeadlineOfferFactory(offer=offer)
factories.HeadlineOfferFactory(offer=offer, timespan=finished_timespan)

headline_offer_query_result = repository.get_active_headline_offer(offer.id)
assert headline_offer_query_result == headline_offer

def test_get_specific_offer_active_headline_offer(self):
offer = factories.OfferFactory(isActive=True)
offer_on_another_venue = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=offer)
factories.StockFactory(offer=offer_on_another_venue)
factories.HeadlineOfferFactory(offer=offer)
factories.HeadlineOfferFactory(offer=offer_on_another_venue)

headline_offer_query_result = repository.get_active_headline_offer(offer.id)
assert headline_offer_query_result.offer == offer

def test_should_return_no_inactive_headline_offer(self):
offer = factories.OfferFactory(isActive=False)
factories.StockFactory(offer=offer)
factories.HeadlineOfferFactory(offer=offer)
creation_time = datetime.datetime.utcnow() - datetime.timedelta(days=20)
finished_timespan = (creation_time, creation_time + datetime.timedelta(days=10))
factories.HeadlineOfferFactory(offer=offer, timespan=finished_timespan)

headline_offer_query_result = repository.get_active_headline_offer(offer.id)
assert headline_offer_query_result == None

def test_get_inactive_headline_offers_basic(self):
inactive_offer = factories.OfferFactory(isActive=False)
inactive_offer_headline_offer = factories.HeadlineOfferFactory(offer=inactive_offer)

active_offer = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=active_offer)
active_offer_headline_offer = factories.HeadlineOfferFactory(offer=active_offer)

finished_timespan = (
datetime.datetime.utcnow() - datetime.timedelta(days=20),
datetime.datetime.utcnow() - datetime.timedelta(days=10),
)
already_inactive_offer_headline_offer = factories.HeadlineOfferFactory(
offer=active_offer, timespan=finished_timespan
)

another_active_offer = factories.OfferFactory(isActive=True)
timespan_finishing_in_the_future = (
datetime.datetime.utcnow() - datetime.timedelta(days=3),
datetime.datetime.utcnow() + datetime.timedelta(days=3),
)
soon_to_be_inactive_timespan = factories.HeadlineOfferFactory(
offer=another_active_offer, timespan=timespan_finishing_in_the_future
)

headline_offer_query_result = repository.get_inactive_headline_offers()
assert headline_offer_query_result == [inactive_offer_headline_offer]

def test_get_inactive_headline_offers_empty_result(self):
offer = factories.OfferFactory(isActive=True)
factories.StockFactory(offer=offer)
factories.HeadlineOfferFactory(offer=offer)

headline_offer_query_result = repository.get_inactive_headline_offers()
assert headline_offer_query_result == []

0 comments on commit 423ca9f

Please sign in to comment.