From e561d56b573708b01e83b253ce45f44242f1fe5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Marie=20CL=C3=89RY?= Date: Thu, 19 Sep 2024 16:32:39 +0200 Subject: [PATCH 1/4] (PC-31953)[PRO] fix: Send address payload in PATCH even if label is empty --- .../__specs__/serializers.spec.ts | 29 +++++++++++++++++++ .../InformationsScreen/serializePatchOffer.ts | 3 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts index 2516f91cab3..8dab404349a 100644 --- a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts +++ b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts @@ -164,6 +164,35 @@ describe('test updateIndividualOffer::serializers', () => { formValues, }) ).toEqual(patchBody) + + // Test with empty label and manual edition false + formValues = { + ...formValues, + city: 'Paris', + latitude: '48.853320', + longitude: '2.348979', + postalCode: '75001', + street: '3 Rue de Valois', + locationLabel: '', + } + patchBody = { + ...patchBody, + address: { + city: 'Paris', + latitude: '48.853320', + longitude: '2.348979', + postalCode: '75001', + street: '3 Rue de Valois', + label: '', + }, + } + + expect( + serializePatchOffer({ + offer: getIndividualOfferFactory(), + formValues, + }) + ).toEqual(patchBody) }) }) }) diff --git a/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts b/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts index 5e1cace1bf6..03e4afc9428 100644 --- a/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts +++ b/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts @@ -83,8 +83,7 @@ export const serializePatchOffer = ({ sentValues.latitude && sentValues.longitude && sentValues.postalCode && - sentValues.street && - sentValues.locationLabel + sentValues.street ) { addressValues = { address: { From f53377e909ba7f03cf10684e77f18fc12d4cfe24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Marie=20CL=C3=89RY?= Date: Thu, 19 Sep 2024 16:39:03 +0200 Subject: [PATCH 2/4] (PC-31953)[PRO] fix: Send `manuallySetAddress` in patch payload --- .../InformationsScreen/__specs__/serializers.spec.ts | 8 ++++++++ .../InformationsScreen/serializePatchOffer.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts index 8dab404349a..d113691745c 100644 --- a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts +++ b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts @@ -146,6 +146,7 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', locationLabel: 'Bureau', + manuallySetAddress: true, } patchBody = { ...patchBody, @@ -156,6 +157,9 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', label: 'Bureau', + // TODO: Remove that TS annotation when types are re-generated + // @ts-expect-error + isManualEdition: true, }, } expect( @@ -174,6 +178,7 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', locationLabel: '', + manuallySetAddress: false, } patchBody = { ...patchBody, @@ -184,6 +189,9 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', label: '', + // TODO: Remove that TS annotation when types are re-generated + // @ts-expect-error + isManualEdition: false, }, } diff --git a/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts b/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts index 03e4afc9428..eb375048628 100644 --- a/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts +++ b/pro/src/screens/IndividualOffer/InformationsScreen/serializePatchOffer.ts @@ -93,6 +93,7 @@ export const serializePatchOffer = ({ postalCode: sentValues.postalCode, street: sentValues.street, label: sentValues.locationLabel, + isManualEdition: sentValues.manuallySetAddress, }, } } From 022bee4886d99f70275c02ae46e120e3b6c9a328 Mon Sep 17 00:00:00 2001 From: Damien Ramelet Date: Thu, 19 Sep 2024 16:53:08 +0200 Subject: [PATCH 3/4] (BSR)[API] chore: Remove useless code This logic is already handle by the ApiAdresse connector --- api/src/pcapi/core/offerers/api.py | 38 ++++++++---------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/api/src/pcapi/core/offerers/api.py b/api/src/pcapi/core/offerers/api.py index 9efdf313cda..67764c889e8 100644 --- a/api/src/pcapi/core/offerers/api.py +++ b/api/src/pcapi/core/offerers/api.py @@ -2729,34 +2729,16 @@ def create_offerer_address_from_address_api( longitude: float, ) -> geography_models.Address: is_manual_edition: bool = False - try: - address_info = api_adresse.get_address(street, postal_code, city) - location_data = LocationData( - city=address_info.city, - postal_code=address_info.postcode, - latitude=address_info.latitude, - longitude=address_info.longitude, - street=address_info.street, - insee_code=address_info.citycode, - ban_id=address_info.id, - ) - except api_adresse.NoResultException: - insee_code = None - if city and postal_code: - try: - insee_code = api_adresse.get_municipality_centroid(city, postal_code).citycode - except api_adresse.AdresseException: - pass - location_data = LocationData( - city=city, - postal_code=postal_code, - latitude=latitude, - longitude=longitude, - street=street, - insee_code=insee_code, - ban_id=None, - ) - is_manual_edition = True + address_info = api_adresse.get_address(street, postal_code, city) + location_data = LocationData( + city=address_info.city, + postal_code=address_info.postcode, + latitude=address_info.latitude, + longitude=address_info.longitude, + street=address_info.street, + insee_code=address_info.citycode, + ban_id=address_info.id, + ) return get_or_create_address(location_data, is_manual_edition=is_manual_edition) From 84f2fd3176ae7070e4885404af6f9ef5ee15d1d4 Mon Sep 17 00:00:00 2001 From: Damien Ramelet Date: Thu, 19 Sep 2024 17:10:20 +0200 Subject: [PATCH 4/4] (PC-31953)[API] feat: Handle manual address edition on pro side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the front doesn’t get successfull results from the API adresse, the user can manually input data. Handle this case while still calling the API adresse to retrieve the inseeCode which is mandatory if we want truthworthy departmentCode and related timezones. Also, calling API adresse with manually input data means higher chances of reaching a `NoResultException`. We don’t want the user to be stuck within offer creation process because of missing data on BAN side. If so, keeping user’s data and let inseeCode to None. (departmentCode & timezone will fallback on postalCode) --- api/src/pcapi/core/offerers/api.py | 45 ++++++-- api/src/pcapi/core/offerers/schemas.py | 1 + api/src/pcapi/core/offers/api.py | 1 + api/tests/routes/pro/patch_offer_test.py | 105 +++++++++++++++++- api/tests/routes/pro/post_offer_test.py | 9 +- .../apiClient/v1/models/AddressBodyModel.ts | 1 + .../__specs__/serializers.spec.ts | 4 - 7 files changed, 142 insertions(+), 24 deletions(-) diff --git a/api/src/pcapi/core/offerers/api.py b/api/src/pcapi/core/offerers/api.py index 67764c889e8..e26f5788fbc 100644 --- a/api/src/pcapi/core/offerers/api.py +++ b/api/src/pcapi/core/offerers/api.py @@ -2727,18 +2727,41 @@ def create_offerer_address_from_address_api( city: str, latitude: float, longitude: float, + is_manual_edition: bool, ) -> geography_models.Address: - is_manual_edition: bool = False - address_info = api_adresse.get_address(street, postal_code, city) - location_data = LocationData( - city=address_info.city, - postal_code=address_info.postcode, - latitude=address_info.latitude, - longitude=address_info.longitude, - street=address_info.street, - insee_code=address_info.citycode, - ban_id=address_info.id, - ) + if is_manual_edition: + try: + address_info = api_adresse.get_municipality_centroid(city=city, postcode=postal_code) + location_data = LocationData( + city=city, + postal_code=postal_code, + latitude=latitude, + longitude=longitude, + street=street, + insee_code=address_info.citycode, + ban_id=None, + ) + except api_adresse.NoResultException: + location_data = LocationData( + city=city, + postal_code=postal_code, + latitude=latitude, + longitude=longitude, + street=street, + insee_code=None, + ban_id=None, + ) + else: + address_info = api_adresse.get_address(street, postal_code, city) + location_data = LocationData( + city=address_info.city, + postal_code=address_info.postcode, + latitude=address_info.latitude, + longitude=address_info.longitude, + street=address_info.street, + insee_code=address_info.citycode, + ban_id=address_info.id, + ) return get_or_create_address(location_data, is_manual_edition=is_manual_edition) diff --git a/api/src/pcapi/core/offerers/schemas.py b/api/src/pcapi/core/offerers/schemas.py index 6815f5853d1..3877cac8681 100644 --- a/api/src/pcapi/core/offerers/schemas.py +++ b/api/src/pcapi/core/offerers/schemas.py @@ -117,3 +117,4 @@ class AddressBodyModel(BaseModel): longitude: float | str postalCode: VenuePostalCode street: VenueAddress + isManualEdition: bool = False diff --git a/api/src/pcapi/core/offers/api.py b/api/src/pcapi/core/offers/api.py index 026cd427b0f..4032dd2f3c5 100644 --- a/api/src/pcapi/core/offers/api.py +++ b/api/src/pcapi/core/offers/api.py @@ -331,6 +331,7 @@ def get_offerer_address_from_address( city=address.city, latitude=float(address.latitude), longitude=float(address.longitude), + is_manual_edition=address.isManualEdition, ) return offerers_api.get_or_create_offerer_address( venue.managingOffererId, diff --git a/api/tests/routes/pro/patch_offer_test.py b/api/tests/routes/pro/patch_offer_test.py index 046fc27e70c..e065a059e6e 100644 --- a/api/tests/routes/pro/patch_offer_test.py +++ b/api/tests/routes/pro/patch_offer_test.py @@ -4,7 +4,7 @@ import pytest -from pcapi.connectors.api_adresse import AddressInfo +from pcapi.connectors import api_adresse import pcapi.core.bookings.factories as bookings_factories from pcapi.core.categories import subcategories_v2 as subcategories import pcapi.core.mails.testing as mails_testing @@ -107,7 +107,7 @@ def test_patch_offer_with_address(self, get_address_mock, label, offer_has_oa, a "label": label, }, } - get_address_mock.return_value = AddressInfo( + get_address_mock.return_value = api_adresse.AddressInfo( street="1 rue de la paix", city="Paris", citycode="75102", @@ -132,6 +132,104 @@ def test_patch_offer_with_address(self, get_address_mock, label, offer_has_oa, a assert address.postalCode == "75102" assert address.latitude == Decimal("48.85660") assert address.longitude == Decimal("2.3522") + assert address.isManualEdition is False + + @patch("pcapi.connectors.api_adresse.get_municipality_centroid") + def test_patch_offer_with_manual_address_edition(self, mocked_get_centroid, client): + # Given + user_offerer = offerers_factories.UserOffererFactory(user__email="user@example.com") + venue = offerers_factories.VenueFactory(managingOfferer=user_offerer.offerer) + offer = offers_factories.OfferFactory( + subcategoryId=subcategories.RENCONTRE.id, + venue=venue, + name="New name", + description="description", + ) + mocked_get_centroid.return_value = api_adresse.AddressInfo( + id="98826", + label="Poum", + postcode="98826", + citycode="98826", + latitude=-20.203, + longitude=164.073, + score=0.9371472727272726, + city="Poum", + street=None, + ) + + # When + data = { + "name": "Visite des Marais Salins de Kô", + "externalTicketOfficeUrl": "http://example.net", + "mentalDisabilityCompliant": True, + "address": { + "street": "3, Chemin de la Plage", + "city": "Poum, Tiabet", + "postalCode": "98826", + "latitude": -20.08521415490879, + "longitude": 164.03239215718415, + "label": "Marais Salins de Kô", + "isManualEdition": True, + }, + } + + response = client.with_session_auth("user@example.com").patch(f"/offers/{offer.id}", json=data) + + assert response.status_code == 200 + assert response.json["id"] == offer.id + updated_offer = Offer.query.get(offer.id) + address = updated_offer.offererAddress.address + assert updated_offer.offererAddress.label == "Marais Salins de Kô" + assert address.street == data["address"]["street"] + assert address.city == data["address"]["city"] + assert address.postalCode == data["address"]["postalCode"] + assert address.inseeCode == "98826" + assert address.latitude == Decimal("-20.08521") + assert address.longitude == Decimal("164.03239") + assert address.isManualEdition is True + + @patch("pcapi.connectors.api_adresse.get_municipality_centroid") + def test_unknown_result_from_api_adresse_doesnt_block_offer_creation(self, mocked_get_centroid, client): + # Given + user_offerer = offerers_factories.UserOffererFactory(user__email="user@example.com") + venue = offerers_factories.VenueFactory(managingOfferer=user_offerer.offerer) + offer = offers_factories.OfferFactory( + subcategoryId=subcategories.RENCONTRE.id, + venue=venue, + name="New name", + description="description", + ) + mocked_get_centroid.side_effect = api_adresse.NoResultException + # When + data = { + "name": "Visite des Marais Salins de Kô", + "externalTicketOfficeUrl": "http://example.net", + "mentalDisabilityCompliant": True, + "address": { + "street": "3, Chemin de la Plage", + "city": "Poum, Tiabet", + "postalCode": "98826", + "latitude": -20.08521415490879, + "longitude": 164.03239215718415, + "label": "Marais Salins de Kô", + "isManualEdition": True, + }, + } + + response = client.with_session_auth("user@example.com").patch(f"/offers/{offer.id}", json=data) + + assert response.status_code == 200 + assert response.json["id"] == offer.id + updated_offer = Offer.query.get(offer.id) + address = updated_offer.offererAddress.address + assert updated_offer.offererAddress.label == "Marais Salins de Kô" + assert address.street == data["address"]["street"] + assert address.city == data["address"]["city"] + assert address.postalCode == data["address"]["postalCode"] + assert address.inseeCode == None + assert address.latitude == Decimal("-20.08521") + assert address.longitude == Decimal("164.03239") + assert address.isManualEdition is True @pytest.mark.parametrize("label", ["", None, True]) @patch("pcapi.connectors.api_adresse.get_address") @@ -170,7 +268,7 @@ def test_patch_offer_with_address_twice(self, get_address_mock, label, client): "label": label, }, } - get_address_mock.return_value = AddressInfo( + get_address_mock.return_value = api_adresse.AddressInfo( street="1 rue de la paix", city="Paris", citycode="75102", @@ -193,6 +291,7 @@ def test_patch_offer_with_address_twice(self, get_address_mock, label, client): assert address.postalCode == "75102" assert address.latitude == Decimal("48.85660") assert address.longitude == Decimal("2.3522") + assert address.isManualEdition is False def test_withdrawal_can_be_updated(self, client): offer = offers_factories.OfferFactory( diff --git a/api/tests/routes/pro/post_offer_test.py b/api/tests/routes/pro/post_offer_test.py index 1a8e830d884..d58ef914e57 100644 --- a/api/tests/routes/pro/post_offer_test.py +++ b/api/tests/routes/pro/post_offer_test.py @@ -1,9 +1,7 @@ from decimal import Decimal -from unittest.mock import patch import pytest -from pcapi.connectors import api_adresse from pcapi.core.categories import subcategories_v2 as subcategories import pcapi.core.offerers.factories as offerers_factories from pcapi.core.offers.models import Offer @@ -210,11 +208,10 @@ def test_create_event_offer_with_manual_offerer_address(self, oa_label, client): "longitude": "2.308289", "postalCode": "75001", "street": "3 Rue de Valois", + "isManualEdition": True, }, } - with patch("pcapi.connectors.api_adresse.get_address") as mocked_get_address: - mocked_get_address.side_effect = api_adresse.NoResultException - response = client.with_session_auth("user@example.com").post("/offers", json=data) + response = client.with_session_auth("user@example.com").post("/offers", json=data) # Then assert response.status_code == 201 @@ -222,8 +219,8 @@ def test_create_event_offer_with_manual_offerer_address(self, oa_label, client): offer = Offer.query.get(offer_id) assert offer.offererAddress.address.isManualEdition assert offer.offererAddress.label == oa_label - assert offer.offererAddress.address.inseeCode == "06029" assert not offer.offererAddress.address.banId + assert offer.offererAddress.address.isManualEdition is True def when_creating_new_thing_offer(self, client): # Given diff --git a/pro/src/apiClient/v1/models/AddressBodyModel.ts b/pro/src/apiClient/v1/models/AddressBodyModel.ts index b191229fa60..2e7bc4f1abe 100644 --- a/pro/src/apiClient/v1/models/AddressBodyModel.ts +++ b/pro/src/apiClient/v1/models/AddressBodyModel.ts @@ -4,6 +4,7 @@ /* eslint-disable */ export type AddressBodyModel = { city: string; + isManualEdition?: boolean; label?: string | null; latitude: (number | string); longitude: (number | string); diff --git a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts index d113691745c..784da6c5279 100644 --- a/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts +++ b/pro/src/screens/IndividualOffer/InformationsScreen/__specs__/serializers.spec.ts @@ -157,8 +157,6 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', label: 'Bureau', - // TODO: Remove that TS annotation when types are re-generated - // @ts-expect-error isManualEdition: true, }, } @@ -189,8 +187,6 @@ describe('test updateIndividualOffer::serializers', () => { postalCode: '75001', street: '3 Rue de Valois', label: '', - // TODO: Remove that TS annotation when types are re-generated - // @ts-expect-error isManualEdition: false, }, }