From cddcea9f696278b62081eb1b3a79c317ccd1bb91 Mon Sep 17 00:00:00 2001 From: jules cicurel <180195494+jcicurel-pass@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:27:43 +0100 Subject: [PATCH] (PC-32262)[API] feat: remove collective offer beginningDatetime in public api --- api/src/pcapi/core/educational/api/offer.py | 15 +++---- api/src/pcapi/core/educational/api/shared.py | 21 +++++---- api/src/pcapi/core/educational/api/stock.py | 3 +- .../public/collective/endpoints/offers.py | 3 +- .../public/collective/serialization/offers.py | 44 ++++--------------- .../public/documentation_constants/fields.py | 8 +--- 6 files changed, 32 insertions(+), 62 deletions(-) diff --git a/api/src/pcapi/core/educational/api/offer.py b/api/src/pcapi/core/educational/api/offer.py index ba3f5dac738..02cd796df2d 100644 --- a/api/src/pcapi/core/educational/api/offer.py +++ b/api/src/pcapi/core/educational/api/offer.py @@ -481,9 +481,8 @@ def create_collective_offer_public( collective_stock = educational_models.CollectiveStock( collectiveOffer=collective_offer, - beginningDatetime=body.beginning_datetime, - startDatetime=body.start_datetime or body.beginning_datetime, - endDatetime=body.end_datetime or body.beginning_datetime, + startDatetime=body.start_datetime, + endDatetime=body.end_datetime, bookingLimitDatetime=body.booking_limit_datetime, price=body.total_price, numberOfTickets=body.number_of_tickets, @@ -570,7 +569,7 @@ def edit_collective_offer_public( offer.offerVenue["otherAddress"] = value.get("otherAddress") or "" elif key == "bookingLimitDatetime" and value is None: offer.collectiveStock.bookingLimitDatetime = new_values.get( - "beginningDatetime", offer.collectiveStock.beginningDatetime + "startDatetime", offer.collectiveStock.startDatetime ) elif key in stock_fields: setattr(offer.collectiveStock, key, value) @@ -582,15 +581,13 @@ def edit_collective_offer_public( api_shared.update_collective_stock_booking( stock=offer.collectiveStock, current_booking=collective_stock_unique_booking, - beginning_datetime_has_changed="beginningDatetime" in new_values, + datetime_has_changed="startDatetime" in new_values, + datetime_column="startDatetime", ) db.session.commit() - notify_educational_redactor_on_collective_offer_or_stock_edit( - offer.id, - updated_fields, - ) + notify_educational_redactor_on_collective_offer_or_stock_edit(offer.id, updated_fields) return offer diff --git a/api/src/pcapi/core/educational/api/shared.py b/api/src/pcapi/core/educational/api/shared.py index 3ae860c5d53..9182854916a 100644 --- a/api/src/pcapi/core/educational/api/shared.py +++ b/api/src/pcapi/core/educational/api/shared.py @@ -1,4 +1,5 @@ import datetime +import typing from pcapi.core.educational import exceptions as educational_exceptions from pcapi.core.educational import models as educational_models @@ -10,7 +11,8 @@ def update_collective_stock_booking( stock: educational_models.CollectiveStock, current_booking: educational_models.CollectiveBooking | None, - beginning_datetime_has_changed: bool, + datetime_has_changed: bool, + datetime_column: typing.Literal["startDatetime"] | typing.Literal["beginningDatetime"], ) -> None: """When a collective stock is updated, we also update some fields of its related booking""" @@ -32,16 +34,17 @@ def update_collective_stock_booking( if booking_to_update: booking_to_update.confirmationLimitDate = booking_limit_value - if beginning_datetime_has_changed: - _update_collective_booking_cancellation_limit_date(booking_to_update, stock.beginningDatetime) - _update_collective_booking_educational_year_id(booking_to_update, stock.beginningDatetime) + if datetime_has_changed: + start = getattr(stock, datetime_column) + _update_collective_booking_cancellation_limit_date(booking_to_update, start) + _update_collective_booking_educational_year_id(booking_to_update, start) def _update_collective_booking_educational_year_id( booking: educational_models.CollectiveBooking, - new_beginning_datetime: datetime.datetime, + new_start_datetime: datetime.datetime, ) -> None: - educational_year = educational_repository.find_educational_year_by_date(new_beginning_datetime) + educational_year = educational_repository.find_educational_year_by_date(new_start_datetime) if educational_year is None: raise educational_exceptions.EducationalYearNotFound() @@ -49,16 +52,16 @@ def _update_collective_booking_educational_year_id( def _update_collective_booking_cancellation_limit_date( - booking: educational_models.CollectiveBooking, new_beginning_datetime: datetime.datetime + booking: educational_models.CollectiveBooking, new_start_datetime: datetime.datetime ) -> None: # if the input date has a timezone (resp. does not have one), we need to compare it with an aware datetime (resp. a naive datetime) now = ( datetime.datetime.utcnow() - if new_beginning_datetime.tzinfo is None + if new_start_datetime.tzinfo is None else datetime.datetime.now(datetime.timezone.utc) # pylint: disable=datetime-now ) booking.cancellationLimitDate = educational_utils.compute_educational_booking_cancellation_limit_date( - new_beginning_datetime, now + new_start_datetime, now ) diff --git a/api/src/pcapi/core/educational/api/stock.py b/api/src/pcapi/core/educational/api/stock.py index 141f43d7029..09d98f2328c 100644 --- a/api/src/pcapi/core/educational/api/stock.py +++ b/api/src/pcapi/core/educational/api/stock.py @@ -213,7 +213,8 @@ def edit_collective_stock( api_shared.update_collective_stock_booking( stock=stock, current_booking=current_booking, - beginning_datetime_has_changed="beginningDatetime" in stock_data, + datetime_has_changed="beginningDatetime" in stock_data, + datetime_column="beginningDatetime", ) db.session.flush() diff --git a/api/src/pcapi/routes/public/collective/endpoints/offers.py b/api/src/pcapi/routes/public/collective/endpoints/offers.py index f2aa02dba37..a6a5a9673af 100644 --- a/api/src/pcapi/routes/public/collective/endpoints/offers.py +++ b/api/src/pcapi/routes/public/collective/endpoints/offers.py @@ -292,7 +292,8 @@ def patch_collective_offer_public( "students", "offerVenue", "interventionArea", - "beginningDatetime", + "startDatetime", + "endDatetime", "totalPrice", "numberOfTickets", "audioDisabilityCompliant", diff --git a/api/src/pcapi/routes/public/collective/serialization/offers.py b/api/src/pcapi/routes/public/collective/serialization/offers.py index f37dcf3ab85..df049280a71 100644 --- a/api/src/pcapi/routes/public/collective/serialization/offers.py +++ b/api/src/pcapi/routes/public/collective/serialization/offers.py @@ -83,24 +83,14 @@ def validate_price(price: float | None) -> float: def validate_booking_limit_datetime(booking_limit_datetime: datetime | None, values: dict[str, Any]) -> datetime | None: if ( - booking_limit_datetime - and "beginning_datetime" in values - and booking_limit_datetime > values["beginning_datetime"] + booking_limit_datetime is not None + and "start_datetime" in values + and booking_limit_datetime > values["start_datetime"] ): raise ValueError("La date limite de réservation ne peut être postérieure à la date de début de l'évènement") return booking_limit_datetime -def validate_beginning_datetime(beginning_datetime: datetime, values: dict[str, Any]) -> datetime: - # we need a datetime with timezone information which is not provided by datetime.utcnow. - if beginning_datetime.tzinfo is not None: - if beginning_datetime < datetime.now(timezone.utc): # pylint: disable=datetime-now - raise ValueError("L'évènement ne peut commencer dans le passé.") - elif beginning_datetime < datetime.utcnow(): - raise ValueError("L'évènement ne peut commencer dans le passé.") - return beginning_datetime - - def validate_start_datetime(start_datetime: datetime | None, values: dict[str, Any]) -> datetime | None: # we need a datetime with timezone information which is not provided by datetime.utcnow. if not start_datetime: @@ -156,10 +146,6 @@ def booking_limit_datetime_validator(field_name: str) -> classmethod: return validator(field_name, allow_reuse=True)(validate_booking_limit_datetime) -def beginning_datetime_validator(field_name: str) -> classmethod: - return validator(field_name, allow_reuse=True)(validate_beginning_datetime) - - def start_datetime_validator(field_name: str) -> classmethod: return validator(field_name, allow_reuse=True)(validate_start_datetime) @@ -199,7 +185,6 @@ class Config: class CollectiveOffersResponseModel(BaseModel): id: int = fields.COLLECTIVE_OFFER_ID - beginningDatetime: str = fields.COLLECTIVE_OFFER_BEGINNING_DATETIME startDatetime: str = fields.COLLECTIVE_OFFER_START_DATETIME endDatetime: str = fields.COLLECTIVE_OFFER_END_DATETIME status: str = fields.COLLECTIVE_OFFER_STATUS @@ -216,7 +201,6 @@ def from_orm(cls, offer: CollectiveOffer) -> "CollectiveOffersResponseModel": ] return cls( id=offer.id, - beginningDatetime=offer.collectiveStock.beginningDatetime.replace(microsecond=0).isoformat(), startDatetime=offer.collectiveStock.startDatetime.replace(microsecond=0).isoformat(), endDatetime=offer.collectiveStock.endDatetime.replace(microsecond=0).isoformat(), status=offer.status.value, @@ -282,7 +266,6 @@ class GetPublicCollectiveOfferResponseModel(BaseModel): mentalDisabilityCompliant: bool | None = fields.MENTAL_DISABILITY_COMPLIANT motorDisabilityCompliant: bool | None = fields.MOTOR_DISABILITY_COMPLIANT visualDisabilityCompliant: bool | None = fields.VISUAL_DISABILITY_COMPLIANT - beginningDatetime: str = fields.COLLECTIVE_OFFER_BEGINNING_DATETIME startDatetime: str = fields.COLLECTIVE_OFFER_START_DATETIME endDatetime: str = fields.COLLECTIVE_OFFER_END_DATETIME bookingLimitDatetime: str = fields.COLLECTIVE_OFFER_BOOKING_LIMIT_DATETIME @@ -333,7 +316,6 @@ def from_orm(cls, offer: CollectiveOffer) -> "GetPublicCollectiveOfferResponseMo mentalDisabilityCompliant=offer.mentalDisabilityCompliant, motorDisabilityCompliant=offer.motorDisabilityCompliant, visualDisabilityCompliant=offer.visualDisabilityCompliant, - beginningDatetime=offer.collectiveStock.beginningDatetime.replace(microsecond=0).isoformat(), startDatetime=offer.collectiveStock.startDatetime.replace(microsecond=0).isoformat(), endDatetime=offer.collectiveStock.endDatetime.replace(microsecond=0).isoformat(), bookingLimitDatetime=offer.collectiveStock.bookingLimitDatetime.replace(microsecond=0).isoformat(), @@ -386,9 +368,8 @@ class PostCollectiveOfferBodyModel(BaseModel): image_credit: str | None = fields.IMAGE_CREDIT nationalProgramId: int | None = fields.COLLECTIVE_OFFER_NATIONAL_PROGRAM_ID # stock part - beginning_datetime: datetime = fields.COLLECTIVE_OFFER_BEGINNING_DATETIME - start_datetime: datetime | None = fields.COLLECTIVE_OFFER_START_DATETIME - end_datetime: datetime | None = fields.COLLECTIVE_OFFER_END_DATETIME + start_datetime: datetime = fields.COLLECTIVE_OFFER_START_DATETIME + end_datetime: datetime = fields.COLLECTIVE_OFFER_END_DATETIME booking_limit_datetime: datetime = fields.COLLECTIVE_OFFER_BOOKING_LIMIT_DATETIME total_price: decimal.Decimal = fields.COLLECTIVE_OFFER_TOTAL_PRICE number_of_tickets: int = fields.COLLECTIVE_OFFER_NB_OF_TICKETS @@ -399,7 +380,6 @@ class PostCollectiveOfferBodyModel(BaseModel): _validate_number_of_tickets = number_of_tickets_validator("number_of_tickets") _validate_total_price = price_validator("total_price") - _validate_beginning_datetime = beginning_datetime_validator("beginning_datetime") _validate_start_datetime = start_datetime_validator("start_datetime") _validate_end_datetime = end_datetime_validator("end_datetime") _validate_booking_limit_datetime = booking_limit_datetime_validator("booking_limit_datetime") @@ -491,7 +471,6 @@ class PatchCollectiveOfferBodyModel(BaseModel): imageFile: str | None = fields.IMAGE_FILE nationalProgramId: int | None = fields.COLLECTIVE_OFFER_NATIONAL_PROGRAM_ID # stock part - beginningDatetime: datetime | None = fields.COLLECTIVE_OFFER_BEGINNING_DATETIME startDatetime: datetime | None = fields.COLLECTIVE_OFFER_START_DATETIME endDatetime: datetime | None = fields.COLLECTIVE_OFFER_END_DATETIME bookingLimitDatetime: datetime | None = fields.COLLECTIVE_OFFER_BOOKING_LIMIT_DATETIME @@ -510,7 +489,6 @@ class PatchCollectiveOfferBodyModel(BaseModel): _validate_number_of_tickets = number_of_tickets_validator("numberOfTickets") _validate_total_price = price_validator("price") _validate_educational_price_detail = price_detail_validator("educationalPriceDetail") - _validate_beginning_datetime = beginning_datetime_validator("beginningDatetime") _validate_start_datetime = start_datetime_validator("startDatetime") _validate_end_datetime = end_datetime_validator("endDatetime") _validate_contact_phone = phone_number_validator("contactPhone") @@ -550,19 +528,13 @@ def validate_booking_limit_datetime( cls, booking_limit_datetime: datetime | None, values: dict[str, Any] ) -> datetime | None: if ( - booking_limit_datetime - and values.get("beginningDatetime", None) is not None - and booking_limit_datetime > values["beginningDatetime"] + booking_limit_datetime is not None + and "startDatetime" in values + and booking_limit_datetime > values["startDatetime"] ): raise ValueError("La date limite de réservation ne peut être postérieure à la date de début de l'évènement") return booking_limit_datetime - @validator("beginningDatetime", pre=True) - def validate_beginning_limit_datetime(cls, beginningDatetime: datetime | None) -> datetime | None: - if beginningDatetime is None: - raise ValueError("La date de début de l'évènement ne peut pas être nulle.") - return beginningDatetime - @validator("startDatetime", pre=True) def validate_start_limit_datetime(cls, startDatetime: datetime | None) -> datetime | None: if startDatetime is None: diff --git a/api/src/pcapi/routes/public/documentation_constants/fields.py b/api/src/pcapi/routes/public/documentation_constants/fields.py index 29570eecffc..33d4f6cc3c3 100644 --- a/api/src/pcapi/routes/public/documentation_constants/fields.py +++ b/api/src/pcapi/routes/public/documentation_constants/fields.py @@ -262,12 +262,8 @@ class SomeOtherResponseModel(BaseModel): description=f"Id of the national program linked to your offer. The national programs list can be found on **[this endpoint (`Get all known national programs`)]({GET_NATIONAL_PROGRAMS_ANCHOR})**", ) COLLECTIVE_OFFER_DATE_CREATED = Field(description="Collective offer creation date") - COLLECTIVE_OFFER_BEGINNING_DATETIME = Field( - description="Collective offer beginning datetime. It cannot be a date in the past. The expected format is **[ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601)** (standard format for timezone aware datetime).", - example=_example_datetime_with_tz, - ) COLLECTIVE_OFFER_START_DATETIME = Field( - description="Collective offer start datetime. Replaces beginning dateime. It cannot be a date in the past. The expected format is **[ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601)** (standard format for timezone aware datetime).", + description="Collective offer start datetime. It cannot be a date in the past. The expected format is **[ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601)** (standard format for timezone aware datetime).", example=_example_datetime_with_tz, ) COLLECTIVE_OFFER_END_DATETIME = Field( @@ -275,7 +271,7 @@ class SomeOtherResponseModel(BaseModel): example=_example_datetime_with_tz, ) COLLECTIVE_OFFER_BOOKING_LIMIT_DATETIME = Field( - description="Booking limit datetime. It must be anterior to the `beginning_datetime`. The expected format is **[ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601)** (standard format for timezone aware datetime).", + description="Booking limit datetime. It must be anterior to the `start_datetime`. The expected format is **[ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601)** (standard format for timezone aware datetime).", example=_example_datetime_with_tz, ) COLLECTIVE_OFFER_TOTAL_PRICE = Field(example=100.00, description="Collective offer price (in €)")