Skip to content

Commit

Permalink
(BSR)[PRO] fix: handling the INACTIVE displayedStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
rprasquier-pass committed Nov 8, 2024
1 parent 31094b6 commit 3b5d404
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 70 deletions.
6 changes: 6 additions & 0 deletions api/src/pcapi/core/educational/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ class ActiveCollectiveOfferFactory(CollectiveOfferFactory):
pass


class InactiveCollectiveOfferFactory(CollectiveOfferFactory):
isActive = False


class ExpiredWithoutBookingCollectiveOfferFactory(CollectiveOfferFactory):
validation = OfferValidationStatus.APPROVED

Expand Down Expand Up @@ -493,6 +497,8 @@ def create_collective_offer_by_status(
return PendingCollectiveOfferFactory(**kwargs)
case CollectiveOfferDisplayedStatus.ACTIVE:
return ActiveCollectiveOfferFactory(**kwargs)
case CollectiveOfferDisplayedStatus.INACTIVE:
return InactiveCollectiveOfferFactory(**kwargs)
case CollectiveOfferDisplayedStatus.EXPIRED:
return ExpiredWithBookingCollectiveOfferFactory(**kwargs)
case CollectiveOfferDisplayedStatus.PREBOOKED:
Expand Down
11 changes: 8 additions & 3 deletions api/src/pcapi/core/educational/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,16 +814,21 @@ def displayedStatus(self) -> CollectiveOfferDisplayedStatus:
case offer_mixin.OfferValidationStatus.REJECTED:
return CollectiveOfferDisplayedStatus.REJECTED
case offer_mixin.OfferValidationStatus.APPROVED:
if not self.isActive:
return CollectiveOfferDisplayedStatus.INACTIVE

last_booking_status = self.lastBookingStatus
has_booking_limit_passed = self.hasBookingLimitDatetimesPassed
has_started = self.hasBeginningDatetimePassed
has_ended = self.hasEndDatetimePassed

match last_booking_status:
case None:
# pylint: disable=using-constant-test
if has_started and feature.FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
return CollectiveOfferDisplayedStatus.CANCELLED

if has_booking_limit_passed:
if has_started and feature.FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
return CollectiveOfferDisplayedStatus.CANCELLED
return CollectiveOfferDisplayedStatus.EXPIRED

return CollectiveOfferDisplayedStatus.ACTIVE
Expand Down Expand Up @@ -854,7 +859,7 @@ def displayedStatus(self) -> CollectiveOfferDisplayedStatus:
self.lastBookingCancellationReason == CollectiveBookingCancellationReasons.EXPIRED
and not has_started
):
# There is script that set the booking status to EXPIRED when the booking is expired.
# There is a script that set the booking status to CANCELLED with cancellation reason EXPIRED when the booking is expired.
# We need to distinguish between an expired booking and a cancelled booking.
return CollectiveOfferDisplayedStatus.EXPIRED
return CollectiveOfferDisplayedStatus.CANCELLED
Expand Down
97 changes: 49 additions & 48 deletions api/src/pcapi/core/offers/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,11 +570,19 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
)
)

if DisplayedStatus.INACTIVE.value in statuses and not FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
on_collective_offer_filters.append(
and_(
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == False,
)
)

if DisplayedStatus.ACTIVE.value in statuses:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == None,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == False,
)
Expand All @@ -583,8 +591,8 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
# With the FF activated, those offers will be CANCELLED
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.CANCELLED,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == False,
Expand All @@ -594,8 +602,8 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
if DisplayedStatus.PREBOOKED.value in statuses:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.PENDING,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == False,
)
Expand All @@ -604,8 +612,8 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
if DisplayedStatus.BOOKED.value in statuses:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.CONFIRMED,
educational_models.CollectiveOffer.hasEndDatetimePassed == False,
)
Expand All @@ -614,24 +622,21 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
if DisplayedStatus.ENDED.value in statuses:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.CONFIRMED,
educational_models.CollectiveOffer.isActive == True,
or_(
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.USED,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.CONFIRMED,
),
educational_models.CollectiveOffer.hasEndDatetimePassed == True,
)
)
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.USED,
)
)
if not FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.REIMBURSED,
)
Expand All @@ -640,93 +645,89 @@ def _filter_collective_offers_by_statuses(query: BaseQuery, statuses: list[str]
if DisplayedStatus.REIMBURSED.value in statuses and FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.REIMBURSED,
)
)

if DisplayedStatus.EXPIRED.value in statuses:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == True,
or_(
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.PENDING,
offer_id_with_booking_status_subquery.c.status == None,
),
educational_models.CollectiveOffer.hasStartDatetimePassed == False,
),
)

if FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.CANCELLED,
offer_id_with_booking_status_subquery.c.cancellationReason
== educational_models.CollectiveBookingCancellationReasons.EXPIRED,
educational_models.CollectiveOffer.isActive == True,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == True,
educational_models.CollectiveOffer.hasStartDatetimePassed == False,
or_(
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.PENDING,
offer_id_with_booking_status_subquery.c.status == None,
),
)
)
else:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == True,
educational_models.CollectiveOffer.hasStartDatetimePassed == False,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.CANCELLED,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == True,
),
offer_id_with_booking_status_subquery.c.cancellationReason
== educational_models.CollectiveBookingCancellationReasons.EXPIRED,
)
)
else:
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
offer_id_with_booking_status_subquery.c.status == None,
educational_models.CollectiveOffer.hasStartDatetimePassed == True,
educational_models.CollectiveOffer.isActive == True,
educational_models.CollectiveOffer.hasBookingLimitDatetimesPassed == True,
or_(
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.CANCELLED,
offer_id_with_booking_status_subquery.c.status
== educational_models.CollectiveBookingStatus.PENDING,
offer_id_with_booking_status_subquery.c.status == None,
),
),
)

if DisplayedStatus.CANCELLED.value in statuses and FeatureToggle.ENABLE_COLLECTIVE_NEW_STATUSES.is_active():
# Cancelled due to expired booking
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.CANCELLED,
offer_id_with_booking_status_subquery.c.cancellationReason
== educational_models.CollectiveBookingCancellationReasons.EXPIRED,
educational_models.CollectiveOffer.hasStartDatetimePassed == True,
)
)

# Cancelled by admin / CA or on ADAGE
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == educational_models.CollectiveBookingStatus.CANCELLED,
offer_id_with_booking_status_subquery.c.cancellationReason
!= educational_models.CollectiveBookingCancellationReasons.EXPIRED,
),
)

# Cancelled due to no booking when the event has started
on_booking_status_filter.append(
and_(
educational_models.CollectiveOffer.isArchived == False,
educational_models.CollectiveOffer.validation == offer_mixin.OfferValidationStatus.APPROVED,
educational_models.CollectiveOffer.isActive == True,
offer_id_with_booking_status_subquery.c.status == None,
educational_models.CollectiveOffer.hasStartDatetimePassed == True,
),
)

if DisplayedStatus.INACTIVE.value in statuses:
# This case is irrelevant for collective offers
on_collective_offer_filters.append(sa.false())

# Add filters on `CollectiveBooking.Status`
if on_booking_status_filter:
substmt = query_with_booking.filter(or_(*on_booking_status_filter)).subquery()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from itertools import count
from itertools import cycle
import typing
from typing import Optional
from typing import NotRequired
from typing import Type
from typing import TypedDict

Expand Down Expand Up @@ -366,6 +366,15 @@ def add_image_to_offer(offer: educational_models.HasImageMixin, image_name: str)
offer.set_image(image=file.read(), credit="CC-BY-SA WIKIPEDIA", crop_params=DO_NOT_CROP)


class OfferAttributes(TypedDict):
bookingLimitDatetime: datetime
beginningDatetime: datetime
endDatetime: datetime
isActive: NotRequired[bool]
bookingFactory: NotRequired[Type[educational_factories.CollectiveBookingFactory]]
cancellationReason: NotRequired[educational_models.CollectiveBookingCancellationReasons]


def create_offers_booking_with_different_displayed_status(
*,
provider: providers_models.Provider,
Expand Down Expand Up @@ -395,13 +404,6 @@ def create_offers_booking_with_different_displayed_status(
yesterday = today - timedelta(days=1)
tomorrow = today + timedelta(days=1)

class OfferAttributes(TypedDict, total=False):
bookingLimitDatetime: datetime
beginningDatetime: datetime
endDatetime: datetime
bookingFactory: Optional[Type[educational_factories.CollectiveBookingFactory]]
cancellationReason: Optional[educational_models.CollectiveBookingCancellationReasons]

options: dict[str, OfferAttributes] = {
# no bookings
"Amsterdam": {
Expand Down Expand Up @@ -519,6 +521,12 @@ class OfferAttributes(TypedDict, total=False):
"bookingFactory": educational_factories.CancelledCollectiveBookingFactory,
"cancellationReason": educational_models.CollectiveBookingCancellationReasons.OFFERER,
},
"Londres": {
"isActive": False,
"bookingLimitDatetime": in_two_weeks,
"beginningDatetime": in_four_weeks,
"endDatetime": in_four_weeks,
},
# with a different end date than the beginning date
"Reykjavik": {
"bookingLimitDatetime": four_weeks_ago,
Expand All @@ -533,12 +541,14 @@ class OfferAttributes(TypedDict, total=False):
beginning_datetime: datetime = attributes["beginningDatetime"]
end_datetime: datetime = attributes["endDatetime"]
booking_limit_datetime: datetime = attributes["bookingLimitDatetime"]
is_active = attributes.get("isActive", None)

stock = educational_factories.CollectiveStockFactory(
collectiveOffer__name=f"La culture à {city}",
collectiveOffer__educational_domains=[next(domains_iterator)],
collectiveOffer__venue=next(venue_iterator),
collectiveOffer__validation=OfferValidationStatus.APPROVED,
collectiveOffer__isActive=is_active,
collectiveOffer__bookingEmails=["[email protected]"],
beginningDatetime=beginning_datetime,
startDatetime=beginning_datetime,
Expand Down
9 changes: 5 additions & 4 deletions api/tests/core/educational/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

pytestmark = pytest.mark.usefixtures("db_session")

ALL_DISPLAYED_STATUSES = set(CollectiveOfferDisplayedStatus) - {CollectiveOfferDisplayedStatus.INACTIVE}
ALL_DISPLAYED_STATUSES = set(CollectiveOfferDisplayedStatus)
NEW_DISPLAYED_STATUSES = {CollectiveOfferDisplayedStatus.CANCELLED, CollectiveOfferDisplayedStatus.REIMBURSED}


class EducationalDepositTest:
Expand Down Expand Up @@ -615,7 +616,7 @@ def test_unique_program_for_an_educational_institution(self):
class CollectiveOfferDisplayedStatusTest:
@pytest.mark.parametrize(
"status",
ALL_DISPLAYED_STATUSES - {CollectiveOfferDisplayedStatus.CANCELLED, CollectiveOfferDisplayedStatus.REIMBURSED},
ALL_DISPLAYED_STATUSES - NEW_DISPLAYED_STATUSES,
)
def test_get_offer_displayed_status(self, status):
offer = factories.create_collective_offer_by_status(status)
Expand Down Expand Up @@ -723,15 +724,15 @@ def test_get_displayed_status_for_inactive_offer_due_to_end_date_passed(self):
class CollectiveOfferAllowedActionsTest:
@pytest.mark.parametrize(
"status",
ALL_DISPLAYED_STATUSES - {CollectiveOfferDisplayedStatus.CANCELLED, CollectiveOfferDisplayedStatus.REIMBURSED},
ALL_DISPLAYED_STATUSES - NEW_DISPLAYED_STATUSES,
)
def test_get_offer_allowed_actions(self, status):
offer = factories.create_collective_offer_by_status(status)
assert offer.allowedActions == list(ALLOWED_ACTIONS_BY_DISPLAYED_STATUS[status])

@pytest.mark.parametrize(
"status",
ALL_DISPLAYED_STATUSES - {CollectiveOfferDisplayedStatus.CANCELLED, CollectiveOfferDisplayedStatus.REIMBURSED},
ALL_DISPLAYED_STATUSES - NEW_DISPLAYED_STATUSES,
)
def test_get_offer_allowed_actions_public_api(self, status):
offer = factories.create_collective_offer_by_status(status)
Expand Down
Loading

0 comments on commit 3b5d404

Please sign in to comment.