diff --git a/api/src/pcapi/routes/native/v1/offers.py b/api/src/pcapi/routes/native/v1/offers.py index 9aadbfef654..8c22897df79 100644 --- a/api/src/pcapi/routes/native/v1/offers.py +++ b/api/src/pcapi/routes/native/v1/offers.py @@ -55,6 +55,22 @@ def get_offer(offer_id: str) -> serializers.OfferResponse: return serializers.OfferResponse.from_orm(offer) +@blueprint.native_route("/offers/stocks", methods=["POST"]) +@spectree_serialize(response_model=serializers.OffersStocksResponse, api=blueprint.api) +def get_offers_showtimes(body: serializers.OffersStocksRequest) -> serializers.OffersStocksResponse: + offer_ids = body.offer_ids + offers = ( + Offer.query.filter(Offer.id.in_(offer_ids)) + .options(joinedload(Offer.stocks).joinedload(Stock.priceCategory).joinedload(PriceCategory.priceCategoryLabel)) + .options(joinedload(Offer.mediations)) + .options(joinedload(Offer.venue).joinedload(Venue.managingOfferer)) + .all() + ) + serialized_offers = [serializers.OfferPreviewResponse.from_orm(offer) for offer in offers] + offers_response = serializers.OffersStocksResponse(offers=serialized_offers) + return offers_response + + @blueprint.native_route("/offer//report", methods=["POST"]) @spectree_serialize(on_success_status=204, api=blueprint.api) @authenticated_and_active_user_required diff --git a/api/src/pcapi/routes/native/v1/serialization/offers.py b/api/src/pcapi/routes/native/v1/serialization/offers.py index 68011b48b0a..44e491429b4 100644 --- a/api/src/pcapi/routes/native/v1/serialization/offers.py +++ b/api/src/pcapi/routes/native/v1/serialization/offers.py @@ -285,6 +285,40 @@ class Config: json_encoders = {datetime: format_into_utc_date} +class OfferPreviewResponse(BaseModel): + @classmethod + def from_orm(cls, offer: Offer) -> "OfferPreviewResponse": + offer_preview = super().from_orm(offer) + offer_preview.stocks = [OfferStockResponse.from_orm(stock) for stock in offer.activeStocks] + + return offer_preview + + id: int + durationMinutes: int | None + extraData: OfferExtraData | None + image: OfferImageResponse | None + # FIXME: (thconte, 24-04-2024): unused for now + # Will be used from the merge of the ticket PC-29406 + last30DaysBookings: int | None + name: str + stocks: list[OfferStockResponse] + + class Config: + orm_mode = True + allow_population_by_field_name = True + + +class OffersStocksResponse(BaseModel): + offers: list[OfferPreviewResponse] + + class Config: + json_encoders = {datetime: format_into_utc_date} + + +class OffersStocksRequest(BaseModel): + offer_ids: list[int] + + class OfferReportRequest(BaseModel): class Config: alias_generator = to_camel diff --git a/api/tests/routes/native/openapi_test.py b/api/tests/routes/native/openapi_test.py index db33fa32609..b03155727d9 100644 --- a/api/tests/routes/native/openapi_test.py +++ b/api/tests/routes/native/openapi_test.py @@ -71,6 +71,30 @@ def test_public_api(client): "title": "ActivityResponseModel", "type": "object", }, + "ActivityTypesResponse": { + "properties": { + "activities": { + "items": {"$ref": "#/components/schemas/ActivityResponseModel"}, + "title": "Activities", + "type": "array", + } + }, + "required": ["activities"], + "title": "ActivityTypesResponse", + "type": "object", + }, + "AudioDisabilityModel": { + "properties": { + "deafAndHardOfHearing": { + "default": ["Non renseign\u00e9"], + "items": {"type": "string"}, + "title": "Deafandhardofhearing", + "type": "array", + } + }, + "title": "AudioDisabilityModel", + "type": "object", + }, "Banner": { "properties": { "name": {"$ref": "#/components/schemas/BannerName"}, @@ -188,9 +212,7 @@ def test_public_api(client): "type": "object", }, "BookingOfferExtraData": { - "properties": { - "ean": {"nullable": True, "title": "Ean", "type": "string"}, - }, + "properties": {"ean": {"nullable": True, "title": "Ean", "type": "string"}}, "title": "BookingOfferExtraData", "type": "object", }, @@ -580,29 +602,6 @@ def test_public_api(client): "enum": ["GRANT_15_17", "GRANT_18"], "title": "DepositType", }, - "SubscriptionStepperResponse": { - "properties": { - "allowedIdentityCheckMethods": { - "items": {"$ref": "#/components/schemas/IdentityCheckMethod"}, - "type": "array", - }, - "maintenancePageType": { - "anyOf": [{"$ref": "#/components/schemas/MaintenancePageType"}], - "nullable": True, - }, - "errorMessage": {"nullable": True, "title": "Errormessage", "type": "string"}, - "subscriptionStepsToDisplay": { - "items": {"$ref": "#/components/schemas/SubscriptionStepDetailsResponse"}, - "title": "Subscriptionstepstodisplay", - "type": "array", - }, - "subtitle": {"nullable": True, "title": "Subtitle", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - "required": ["subscriptionStepsToDisplay", "allowedIdentityCheckMethods", "title"], - "title": "SubscriptionStepperResponse", - "type": "object", - }, "DomainsCredit": { "properties": { "all": {"$ref": "#/components/schemas/Credit"}, @@ -632,6 +631,11 @@ def test_public_api(client): "title": "E2EUbbleIdCheck", "type": "object", }, + "EligibilityType": { + "description": "An enumeration.", + "enum": ["underage", "age-18"], + "title": "EligibilityType", + }, "EmailChangeConfirmationResponse": { "properties": { "accessToken": {"title": "Accesstoken", "type": "string"}, @@ -643,11 +647,6 @@ def test_public_api(client): "title": "EmailChangeConfirmationResponse", "type": "object", }, - "EligibilityType": { - "description": "An enumeration.", - "enum": ["underage", "age-18"], - "title": "EligibilityType", - }, "EmailHistoryEventTypeEnum": { "description": "An enumeration.", "enum": [ @@ -677,9 +676,9 @@ def test_public_api(client): "expired": {"title": "Expired", "type": "boolean"}, "hasRecentlyResetPassword": {"title": "Hasrecentlyresetpassword", "type": "boolean"}, "newEmail": {"nullable": True, "title": "Newemail", "type": "string"}, + "resetPasswordToken": {"nullable": True, "title": "Resetpasswordtoken", "type": "string"}, "status": {"$ref": "#/components/schemas/EmailHistoryEventTypeEnum"}, "token": {"nullable": True, "title": "Token", "type": "string"}, - "resetPasswordToken": {"nullable": True, "title": "Resetpasswordtoken", "type": "string"}, }, "required": ["expired", "status", "hasRecentlyResetPassword"], "title": "EmailUpdateStatusResponse", @@ -716,16 +715,26 @@ def test_public_api(client): "title": "Isaccessibleaudiodisability", "type": "boolean", }, - "mentalDisability": { - "allOf": [{"$ref": "#/components/schemas/MentalDisabilityModel"}], - "default": {"trainedPersonnel": "Non renseign\u00e9"}, - "title": "Mentaldisability", - }, "isAccessibleMentalDisability": { "default": False, "title": "Isaccessiblementaldisability", "type": "boolean", }, + "isAccessibleMotorDisability": { + "default": False, + "title": "Isaccessiblemotordisability", + "type": "boolean", + }, + "isAccessibleVisualDisability": { + "default": False, + "title": "Isaccessiblevisualdisability", + "type": "boolean", + }, + "mentalDisability": { + "allOf": [{"$ref": "#/components/schemas/MentalDisabilityModel"}], + "default": {"trainedPersonnel": "Non renseign\u00e9"}, + "title": "Mentaldisability", + }, "motorDisability": { "allOf": [{"$ref": "#/components/schemas/MotorDisabilityModel"}], "default": { @@ -736,11 +745,6 @@ def test_public_api(client): }, "title": "Motordisability", }, - "isAccessibleMotorDisability": { - "default": False, - "title": "Isaccessiblemotordisability", - "type": "boolean", - }, "visualDisability": { "allOf": [{"$ref": "#/components/schemas/VisualDisabilityModel"}], "default": { @@ -749,11 +753,6 @@ def test_public_api(client): }, "title": "Visualdisability", }, - "isAccessibleVisualDisability": { - "default": False, - "title": "Isaccessiblevisualdisability", - "type": "boolean", - }, }, "title": "ExternalAccessibilityDataModel", "type": "object", @@ -883,9 +882,9 @@ def test_public_api(client): }, "GoogleAccountRequest": { "properties": { + "accountCreationToken": {"title": "Accountcreationtoken", "type": "string"}, "appsFlyerPlatform": {"nullable": True, "title": "Appsflyerplatform", "type": "string"}, "appsFlyerUserId": {"nullable": True, "title": "Appsflyeruserid", "type": "string"}, - "accountCreationToken": {"title": "Accountcreationtoken", "type": "string"}, "birthdate": {"format": "date", "title": "Birthdate", "type": "string"}, "firebasePseudoId": {"nullable": True, "title": "Firebasepseudoid", "type": "string"}, "marketingEmailSubscription": { @@ -919,6 +918,18 @@ def test_public_api(client): "title": "GoogleSigninRequest", "type": "object", }, + "GtlLabels": { + "properties": { + "label": {"title": "Label", "type": "string"}, + "level01Label": {"nullable": True, "title": "Level01Label", "type": "string"}, + "level02Label": {"nullable": True, "title": "Level02Label", "type": "string"}, + "level03Label": {"nullable": True, "title": "Level03Label", "type": "string"}, + "level04Label": {"nullable": True, "title": "Level04Label", "type": "string"}, + }, + "required": ["label"], + "title": "GtlLabels", + "type": "object", + }, "HomepageLabelResponseModelv2": { "properties": { "name": {"$ref": "#/components/schemas/_HomepageLabelNameEnumv2"}, @@ -950,6 +961,27 @@ def test_public_api(client): "enum": ["with-dms", "without-dms"], "title": "MaintenancePageType", }, + "MentalDisabilityModel": { + "properties": { + "trainedPersonnel": { + "default": "Non renseign\u00e9", + "title": "Trainedpersonnel", + "type": "string", + } + }, + "title": "MentalDisabilityModel", + "type": "object", + }, + "MotorDisabilityModel": { + "properties": { + "entrance": {"default": "Non renseign\u00e9", "title": "Entrance", "type": "string"}, + "exterior": {"default": "Non renseign\u00e9", "title": "Exterior", "type": "string"}, + "facilities": {"default": "Non renseign\u00e9", "title": "Facilities", "type": "string"}, + "parking": {"default": "Non renseign\u00e9", "title": "Parking", "type": "string"}, + }, + "title": "MotorDisabilityModel", + "type": "object", + }, "MovieType": { "properties": { "label": {"title": "Label", "type": "string"}, @@ -1073,10 +1105,7 @@ def test_public_api(client): "title": "SubscriptionMessage", }, }, - "required": [ - "allowedIdentityCheckMethods", - "hasIdentityCheckPending", - ], + "required": ["allowedIdentityCheckMethods", "hasIdentityCheckPending"], "title": "NextSubscriptionStepResponse", "type": "object", }, @@ -1153,6 +1182,32 @@ def test_public_api(client): "title": "OfferOffererResponse", "type": "object", }, + "OfferPreviewResponse": { + "properties": { + "durationMinutes": {"nullable": True, "title": "Durationminutes", "type": "integer"}, + "extraData": { + "anyOf": [{"$ref": "#/components/schemas/OfferExtraData"}], + "nullable": True, + "title": "OfferExtraData", + }, + "id": {"title": "Id", "type": "integer"}, + "image": { + "anyOf": [{"$ref": "#/components/schemas/OfferImageResponse"}], + "nullable": True, + "title": "OfferImageResponse", + }, + "last30DaysBookings": {"nullable": True, "title": "Last30Daysbookings", "type": "integer"}, + "name": {"title": "Name", "type": "string"}, + "stocks": { + "items": {"$ref": "#/components/schemas/OfferStockResponse"}, + "title": "Stocks", + "type": "array", + }, + }, + "required": ["id", "name", "stocks"], + "title": "OfferPreviewResponse", + "type": "object", + }, "OfferReportReasons": { "properties": { "reasons": { @@ -1312,6 +1367,24 @@ def test_public_api(client): "title": "OfferVenueResponse", "type": "object", }, + "OffersStocksRequest": { + "properties": {"offer_ids": {"items": {"type": "integer"}, "title": "Offer Ids", "type": "array"}}, + "required": ["offer_ids"], + "title": "OffersStocksRequest", + "type": "object", + }, + "OffersStocksResponse": { + "properties": { + "offers": { + "items": {"$ref": "#/components/schemas/OfferPreviewResponse"}, + "title": "Offers", + "type": "array", + } + }, + "required": ["offers"], + "title": "OffersStocksResponse", + "type": "object", + }, "OnlineOfflinePlatformChoicesEnumv2": { "description": "An enumeration.", "enum": ["OFFLINE", "ONLINE", "ONLINE_OR_OFFLINE"], @@ -1349,63 +1422,26 @@ def test_public_api(client): "additionalProperties": False, "properties": { "categories": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Categories", "type": "array", }, - "endDate": { - "nullable": True, - "title": "Enddate", - "type": "string", - }, - "isDuo": { - "nullable": True, - "title": "Isduo", - "type": "boolean", - }, - "isEvent": { - "nullable": True, - "title": "Isevent", - "type": "boolean", - }, - "isRecoShuffled": { - "nullable": True, - "title": "Isrecoshuffled", - "type": "boolean", - }, + "endDate": {"nullable": True, "title": "Enddate", "type": "string"}, + "isDuo": {"nullable": True, "title": "Isduo", "type": "boolean"}, + "isEvent": {"nullable": True, "title": "Isevent", "type": "boolean"}, + "isRecoShuffled": {"nullable": True, "title": "Isrecoshuffled", "type": "boolean"}, "offerTypeList": { - "items": { - "additionalProperties": { - "type": "string", - }, - "type": "object", - }, + "items": {"additionalProperties": {"type": "string"}, "type": "object"}, "nullable": True, "title": "Offertypelist", "type": "array", }, - "priceMax": { - "nullable": True, - "title": "Pricemax", - "type": "number", - }, - "priceMin": { - "nullable": True, - "title": "Pricemin", - "type": "number", - }, - "startDate": { - "nullable": True, - "title": "Startdate", - "type": "string", - }, + "priceMax": {"nullable": True, "title": "Pricemax", "type": "number"}, + "priceMin": {"nullable": True, "title": "Pricemin", "type": "number"}, + "startDate": {"nullable": True, "title": "Startdate", "type": "string"}, "subcategories": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Subcategories", "type": "array", @@ -1417,21 +1453,9 @@ def test_public_api(client): "PlaylistRequestQuery": { "additionalProperties": False, "properties": { - "latitude": { - "nullable": True, - "title": "Latitude", - "type": "number", - }, - "longitude": { - "nullable": True, - "title": "Longitude", - "type": "number", - }, - "modelEndpoint": { - "nullable": True, - "title": "Modelendpoint", - "type": "string", - }, + "latitude": {"nullable": True, "title": "Latitude", "type": "number"}, + "longitude": {"nullable": True, "title": "Longitude", "type": "number"}, + "modelEndpoint": {"nullable": True, "title": "Modelendpoint", "type": "string"}, }, "title": "PlaylistRequestQuery", "type": "object", @@ -1441,18 +1465,6 @@ def test_public_api(client): "enum": ["INFO", "ERROR", "WARNING", "CLOCK", "FILE", "MAGNIFYING_GLASS"], "title": "PopOverIcon", }, - "ActivityTypesResponse": { - "properties": { - "activities": { - "items": {"$ref": "#/components/schemas/ActivityResponseModel"}, - "title": "Activities", - "type": "array", - }, - }, - "required": ["activities"], - "title": "ActivityTypesResponse", - "type": "object", - }, "ProfileUpdateRequest": { "properties": { "activityId": {"$ref": "#/components/schemas/ActivityIdEnum"}, @@ -1588,30 +1600,6 @@ def test_public_api(client): "title": "SendPhoneValidationRequest", "type": "object", }, - "ShowSubType": { - "properties": { - "code": {"title": "Code", "type": "integer"}, - "label": {"title": "Label", "type": "string"}, - "slug": {"title": "Slug", "type": "string"}, - }, - "required": ["code", "label", "slug"], - "title": "ShowSubType", - "type": "object", - }, - "ShowType": { - "properties": { - "children": { - "items": {"$ref": "#/components/schemas/ShowSubType"}, - "title": "Children", - "type": "array", - }, - "code": {"title": "Code", "type": "integer"}, - "label": {"title": "Label", "type": "string"}, - }, - "required": ["children", "code", "label"], - "title": "ShowType", - "type": "object", - }, "SettingsResponse": { "properties": { "accountCreationMinimumAge": {"title": "Accountcreationminimumage", "type": "integer"}, @@ -1645,6 +1633,30 @@ def test_public_api(client): "title": "SettingsResponse", "type": "object", }, + "ShowSubType": { + "properties": { + "code": {"title": "Code", "type": "integer"}, + "label": {"title": "Label", "type": "string"}, + "slug": {"title": "Slug", "type": "string"}, + }, + "required": ["code", "label", "slug"], + "title": "ShowSubType", + "type": "object", + }, + "ShowType": { + "properties": { + "children": { + "items": {"$ref": "#/components/schemas/ShowSubType"}, + "title": "Children", + "type": "array", + }, + "code": {"title": "Code", "type": "integer"}, + "label": {"title": "Label", "type": "string"}, + }, + "required": ["children", "code", "label"], + "title": "ShowType", + "type": "object", + }, "SigninRequest": { "properties": { "deviceInfo": { @@ -1674,27 +1686,15 @@ def test_public_api(client): "additionalProperties": False, "properties": { "categories": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Categories", "type": "array", }, - "latitude": { - "nullable": True, - "title": "Latitude", - "type": "number", - }, - "longitude": { - "nullable": True, - "title": "Longitude", - "type": "number", - }, + "latitude": {"nullable": True, "title": "Latitude", "type": "number"}, + "longitude": {"nullable": True, "title": "Longitude", "type": "number"}, "subcategories": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Subcategories", "type": "array", @@ -1939,7 +1939,7 @@ def test_public_api(client): "nullable": True, "title": "CallToActionMessage", }, - "messageSummary": {"title": "Messagesummary", "type": "string", "nullable": True}, + "messageSummary": {"nullable": True, "title": "Messagesummary", "type": "string"}, "popOverIcon": {"anyOf": [{"$ref": "#/components/schemas/PopOverIcon"}], "nullable": True}, "updatedAt": {"format": "date-time", "nullable": True, "title": "Updatedat", "type": "string"}, "userMessage": {"title": "Usermessage", "type": "string"}, @@ -1986,6 +1986,29 @@ def test_public_api(client): "enum": ["Numéro de téléphone", "Profil", "Identification", "Confirmation"], "title": "SubscriptionStepTitle", }, + "SubscriptionStepperResponse": { + "properties": { + "allowedIdentityCheckMethods": { + "items": {"$ref": "#/components/schemas/IdentityCheckMethod"}, + "type": "array", + }, + "errorMessage": {"nullable": True, "title": "Errormessage", "type": "string"}, + "maintenancePageType": { + "anyOf": [{"$ref": "#/components/schemas/MaintenancePageType"}], + "nullable": True, + }, + "subscriptionStepsToDisplay": { + "items": {"$ref": "#/components/schemas/SubscriptionStepDetailsResponse"}, + "title": "Subscriptionstepstodisplay", + "type": "array", + }, + "subtitle": {"nullable": True, "title": "Subtitle", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + "required": ["subscriptionStepsToDisplay", "allowedIdentityCheckMethods", "title"], + "title": "SubscriptionStepperResponse", + "type": "object", + }, "SubscriptionStepperResponseV2": { "properties": { "allowedIdentityCheckMethods": { @@ -2300,9 +2323,9 @@ def test_public_api(client): "latitude": {"nullable": True, "title": "Latitude", "type": "number"}, "longitude": {"nullable": True, "title": "Longitude", "type": "number"}, "name": {"title": "Name", "type": "string"}, + "openingHours": {"nullable": True, "title": "Openinghours", "type": "object"}, "postalCode": {"nullable": True, "title": "Postalcode", "type": "string"}, "publicName": {"nullable": True, "title": "Publicname", "type": "string"}, - "openingHours": {"nullable": True, "title": "Openinghours", "type": "object"}, "street": {"nullable": True, "title": "Street", "type": "string"}, "venueTypeCode": {"$ref": "#/components/schemas/VenueTypeCodeKey"}, "withdrawalDetails": {"nullable": True, "title": "Withdrawaldetails", "type": "string"}, @@ -2338,6 +2361,19 @@ def test_public_api(client): ], "title": "VenueTypeCodeKey", }, + "VisualDisabilityModel": { + "properties": { + "audioDescription": { + "default": ["Non renseign\u00e9"], + "items": {"type": "string"}, + "title": "Audiodescription", + "type": "array", + }, + "soundBeacon": {"default": "Non renseign\u00e9", "title": "Soundbeacon", "type": "string"}, + }, + "title": "VisualDisabilityModel", + "type": "object", + }, "WithdrawalTypeEnum": { "description": "An enumeration.", "enum": ["by_email", "in_app", "no_ticket", "on_site"], @@ -2384,64 +2420,6 @@ def test_public_api(client): ], "title": "(HomepageLabelNameEnumv2", }, - "AudioDisabilityModel": { - "properties": { - "deafAndHardOfHearing": { - "default": ["Non renseign\u00e9"], - "title": "Deafandhardofhearing", - "items": {"type": "string"}, - "type": "array", - } - }, - "title": "AudioDisabilityModel", - "type": "object", - }, - "MentalDisabilityModel": { - "properties": { - "trainedPersonnel": { - "default": "Non renseign\u00e9", - "title": "Trainedpersonnel", - "type": "string", - } - }, - "title": "MentalDisabilityModel", - "type": "object", - }, - "MotorDisabilityModel": { - "properties": { - "entrance": {"default": "Non renseign\u00e9", "title": "Entrance", "type": "string"}, - "exterior": {"default": "Non renseign\u00e9", "title": "Exterior", "type": "string"}, - "facilities": {"default": "Non renseign\u00e9", "title": "Facilities", "type": "string"}, - "parking": {"default": "Non renseign\u00e9", "title": "Parking", "type": "string"}, - }, - "title": "MotorDisabilityModel", - "type": "object", - }, - "VisualDisabilityModel": { - "properties": { - "audioDescription": { - "default": ["Non renseign\u00e9"], - "items": {"type": "string"}, - "title": "Audiodescription", - "type": "array", - }, - "soundBeacon": {"default": "Non renseign\u00e9", "title": "Soundbeacon", "type": "string"}, - }, - "title": "VisualDisabilityModel", - "type": "object", - }, - "GtlLabels": { - "properties": { - "label": {"title": "Label", "type": "string"}, - "level01Label": {"nullable": True, "title": "Level01Label", "type": "string"}, - "level02Label": {"nullable": True, "title": "Level02Label", "type": "string"}, - "level03Label": {"nullable": True, "title": "Level03Label", "type": "string"}, - "level04Label": {"nullable": True, "title": "Level04Label", "type": "string"}, - }, - "required": ["label"], - "title": "GtlLabels", - "type": "object", - }, }, "securitySchemes": {"JWTAuth": {"bearerFormat": "JWT", "scheme": "bearer", "type": "http"}}, }, @@ -3061,16 +3039,16 @@ def test_public_api(client): }, "description": "OK", }, - "400": {"description": "Bad " "Request"}, + "400": {"description": "Bad Request"}, "403": {"description": "Forbidden"}, "422": { "content": { "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} }, - "description": "Unprocessable " "Entity", + "description": "Unprocessable Entity", }, }, - "summary": "create_account_with_google_sso " "", + "summary": "create_account_with_google_sso ", "tags": [], } }, @@ -3122,10 +3100,10 @@ def test_public_api(client): "content": { "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} }, - "description": "Unprocessable " "Entity", + "description": "Unprocessable Entity", }, }, - "summary": "google_oauth_state " "", + "summary": "google_oauth_state ", "tags": [], } }, @@ -3243,6 +3221,35 @@ def test_public_api(client): "tags": [], } }, + "/native/v1/offers/stocks": { + "post": { + "description": "", + "operationId": "post__native_v1_offers_stocks", + "parameters": [], + "requestBody": { + "content": { + "application/json": {"schema": {"$ref": "#/components/schemas/OffersStocksRequest"}} + } + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"$ref": "#/components/schemas/OffersStocksResponse"}} + }, + "description": "OK", + }, + "403": {"description": "Forbidden"}, + "422": { + "content": { + "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} + }, + "description": "Unprocessable Entity", + }, + }, + "summary": "get_offers_showtimes ", + "tags": [], + } + }, "/native/v1/phone_validation/remaining_attempts": { "get": { "description": "", @@ -3466,70 +3473,42 @@ def test_public_api(client): "in": "query", "name": "modelEndpoint", "required": False, - "schema": { - "nullable": True, - "title": "Modelendpoint", - "type": "string", - }, + "schema": {"nullable": True, "title": "Modelendpoint", "type": "string"}, }, { "description": "", "in": "query", "name": "longitude", "required": False, - "schema": { - "nullable": True, - "title": "Longitude", - "type": "number", - }, + "schema": {"nullable": True, "title": "Longitude", "type": "number"}, }, { "description": "", "in": "query", "name": "latitude", "required": False, - "schema": { - "nullable": True, - "title": "Latitude", - "type": "number", - }, + "schema": {"nullable": True, "title": "Latitude", "type": "number"}, }, ], "requestBody": { "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlaylistRequestBody", - }, - }, - }, + "application/json": {"schema": {"$ref": "#/components/schemas/PlaylistRequestBody"}} + } }, "responses": { - "200": { - "description": "OK", - }, - "403": { - "description": "Forbidden", - }, + "200": {"description": "OK"}, + "403": {"description": "Forbidden"}, "422": { "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationError", - }, - }, + "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} }, "description": "Unprocessable Entity", }, }, - "security": [ - { - "JWTAuth": [], - }, - ], + "security": [{"JWTAuth": []}], "summary": "playlist ", "tags": [], - }, + } }, "/native/v1/recommendation/similar_offers/{offer_id}": { "get": { @@ -3541,32 +3520,21 @@ def test_public_api(client): "in": "path", "name": "offer_id", "required": True, - "schema": { - "format": "int32", - "type": "integer", - }, + "schema": {"format": "int32", "type": "integer"}, }, { "description": "", "in": "query", "name": "longitude", "required": False, - "schema": { - "nullable": True, - "title": "Longitude", - "type": "number", - }, + "schema": {"nullable": True, "title": "Longitude", "type": "number"}, }, { "description": "", "in": "query", "name": "latitude", "required": False, - "schema": { - "nullable": True, - "title": "Latitude", - "type": "number", - }, + "schema": {"nullable": True, "title": "Latitude", "type": "number"}, }, { "description": "", @@ -3574,9 +3542,7 @@ def test_public_api(client): "name": "categories", "required": False, "schema": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Categories", "type": "array", @@ -3588,9 +3554,7 @@ def test_public_api(client): "name": "subcategories", "required": False, "schema": { - "items": { - "type": "string", - }, + "items": {"type": "string"}, "nullable": True, "title": "Subcategories", "type": "array", @@ -3598,26 +3562,18 @@ def test_public_api(client): }, ], "responses": { - "200": { - "description": "OK", - }, - "403": { - "description": "Forbidden", - }, + "200": {"description": "OK"}, + "403": {"description": "Forbidden"}, "422": { "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationError", - }, - }, + "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} }, "description": "Unprocessable Entity", }, }, "summary": "similar_offers ", "tags": [], - }, + } }, "/native/v1/refresh_access_token": { "post": { @@ -4197,7 +4153,7 @@ def test_public_api(client): }, "summary": "get_venue ", "tags": [], - }, + } }, "/native/v2/profile/email_update/confirm": { "post": { @@ -4288,35 +4244,23 @@ def test_public_api(client): "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/EmailUpdateStatusResponse", - }, - }, + "schema": {"$ref": "#/components/schemas/EmailUpdateStatusResponse"} + } }, "description": "OK", }, - "403": { - "description": "Forbidden", - }, + "403": {"description": "Forbidden"}, "422": { "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationError", - }, - }, + "application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}} }, "description": "Unprocessable Entity", }, }, - "security": [ - { - "JWTAuth": [], - }, - ], + "security": [{"JWTAuth": []}], "summary": "get_email_update_status ", "tags": [], - }, + } }, "/native/v2/profile/update_email": { "post": { diff --git a/api/tests/routes/native/v1/offers_test.py b/api/tests/routes/native/v1/offers_test.py index f1a64b16765..8a83fa2c6f0 100644 --- a/api/tests/routes/native/v1/offers_test.py +++ b/api/tests/routes/native/v1/offers_test.py @@ -551,6 +551,191 @@ def should_not_update_offer_stocks_when_getting_offer(self, client): assert len(offer.stocks) == 2 +class OffersStocksTest: + def test_return_empty_on_empty_request(self, client): + response = client.post("/native/v1/offers/stocks", json={"offer_ids": []}) + + assert response.status_code == 200 + assert response.json == {"offers": []} + + def test_return_empty_on_not_found(self, client): + response = client.post("/native/v1/offers/stocks", json={"offer_ids": [123456789]}) + + assert response.status_code == 200 + assert response.json == {"offers": []} + + @time_machine.travel("2024-04-01", tick=False) + def test_return_offers_stocks(self, client): + extra_data = { + "allocineId": 12345, + "author": "mandibule", + "ean": "3838", + "musicSubType": "502", + "musicType": "501", + "performer": "interprète", + "showSubType": "101", + "showType": "100", + "stageDirector": "metteur en scène", + "speaker": "intervenant", + "visa": "vasi", + "genres": ["ACTION", "DRAMA"], + "cast": ["cast1", "cast2"], + "editeur": "editeur", + "gtl_id": "01030000", + "releaseDate": "2020-01-01", + } + offer = offers_factories.OfferFactory( + subcategoryId=subcategories.SEANCE_CINE.id, + name="l'offre du siècle", + extraData=extra_data, + durationMinutes=33, + ) + offers_factories.MediationFactory(id=111, offer=offer, thumbCount=1, credit="street credit") + + bookable_stock = offers_factories.EventStockFactory( + offer=offer, + price=12.34, + quantity=2, + priceCategory__priceCategoryLabel__label="bookable", + features=[ + cinema_providers_constants.ShowtimeFeatures.VF.value, + cinema_providers_constants.ShowtimeFeatures.THREE_D.value, + cinema_providers_constants.ShowtimeFeatures.ICE.value, + ], + ) + another_bookable_stock = offers_factories.EventStockFactory( + offer=offer, + price=12.34, + quantity=3, + priceCategory=bookable_stock.priceCategory, + features=[ + cinema_providers_constants.ShowtimeFeatures.VO.value, + cinema_providers_constants.ShowtimeFeatures.THREE_D.value, + ], + ) + expired_stock = offers_factories.EventStockFactory( + offer=offer, + price=45.67, + beginningDatetime=datetime.utcnow() - timedelta(days=1), + priceCategory__priceCategoryLabel__label="expired", + features=[ + cinema_providers_constants.ShowtimeFeatures.VF.value, + cinema_providers_constants.ShowtimeFeatures.ICE.value, + ], + ) + exhausted_stock = offers_factories.EventStockFactory( + offer=offer, + price=89.00, + quantity=1, + priceCategory__priceCategoryLabel__label="exhausted", + features=[cinema_providers_constants.ShowtimeFeatures.VO.value], + ) + + BookingFactory(stock=bookable_stock, user__deposit__expirationDate=datetime(year=2031, month=12, day=31)) + BookingFactory(stock=exhausted_stock, user__deposit__expirationDate=datetime(year=2031, month=12, day=31)) + + payload = {"offer_ids": [offer.id]} + + with assert_num_queries(1): + response = client.post("/native/v1/offers/stocks", json=payload) + + # For the test to be deterministic + response.json["offers"][0]["stocks"].sort(key=lambda stock: stock["id"]) + + assert response.status_code == 200 + assert response.json["offers"][0] == { + "durationMinutes": 33, + "extraData": { + "allocineId": 12345, + "author": "mandibule", + "ean": "3838", + "durationMinutes": None, + "musicSubType": "Acid Jazz", + "musicType": "Jazz", + "performer": "interprète", + "showSubType": "Carnaval", + "showType": "Arts de la rue", + "speaker": "intervenant", + "stageDirector": "metteur en scène", + "visa": "vasi", + "genres": ["Action", "Drame"], + "cast": ["cast1", "cast2"], + "editeur": "editeur", + "gtlLabels": None, + "releaseDate": "2020-01-01", + }, + "id": offer.id, + "image": {"credit": "street credit", "url": "http://localhost/storage/thumbs/mediations/N4"}, + "last30DaysBookings": None, + "name": "l'offre du siècle", + "stocks": sorted( + [ + { + "id": bookable_stock.id, + "price": 1234, + "beginningDatetime": "2024-05-01T00:00:00Z", + "bookingLimitDatetime": "2024-04-30T23:00:00Z", + "cancellationLimitDatetime": "2024-04-03T00:00:00Z", + "features": ["VF", "3D", "ICE"], + "isBookable": True, + "isForbiddenToUnderage": False, + "isSoldOut": False, + "isExpired": False, + "activationCode": None, + "priceCategoryLabel": "bookable", + "remainingQuantity": 1, + }, + { + "id": another_bookable_stock.id, + "price": 1234, + "beginningDatetime": "2024-05-01T00:00:00Z", + "bookingLimitDatetime": "2024-04-30T23:00:00Z", + "cancellationLimitDatetime": "2024-04-03T00:00:00Z", + "features": ["VO", "3D"], + "isBookable": True, + "isForbiddenToUnderage": False, + "isSoldOut": False, + "isExpired": False, + "activationCode": None, + "priceCategoryLabel": "bookable", + "remainingQuantity": 3, + }, + { + "id": expired_stock.id, + "price": 4567, + "beginningDatetime": "2024-03-31T00:00:00Z", + "bookingLimitDatetime": "2024-03-30T23:00:00Z", + "cancellationLimitDatetime": "2024-04-01T00:00:00Z", + "features": ["VF", "ICE"], + "isBookable": False, + "isForbiddenToUnderage": False, + "isSoldOut": True, + "isExpired": True, + "activationCode": None, + "priceCategoryLabel": "expired", + "remainingQuantity": 1000, + }, + { + "id": exhausted_stock.id, + "price": 8900, + "beginningDatetime": "2024-05-01T00:00:00Z", + "bookingLimitDatetime": "2024-04-30T23:00:00Z", + "cancellationLimitDatetime": "2024-04-03T00:00:00Z", + "features": ["VO"], + "isBookable": False, + "isForbiddenToUnderage": False, + "isSoldOut": True, + "isExpired": False, + "activationCode": None, + "priceCategoryLabel": "exhausted", + "remainingQuantity": 0, + }, + ], + key=lambda stock: stock["id"], + ), + } + + class SendOfferWebAppLinkTest: def test_sendinblue_send_offer_webapp_link_by_email(self, client): """