From 62e683f3783a24617ff3697a6c8ceaa0b8a8b379 Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Fri, 1 Feb 2019 15:25:35 +0100
Subject: [PATCH 1/2] planitech: getreservationprice endpoint (#30261)

---
 passerelle/contrib/planitech/models.py | 131 ++++++++++++++++++++++---
 tests/test_planitech.py                |  87 ++++++++++++++++
 2 files changed, 204 insertions(+), 14 deletions(-)

diff --git a/passerelle/contrib/planitech/models.py b/passerelle/contrib/planitech/models.py
index 2114bfa..a9ae732 100644
--- a/passerelle/contrib/planitech/models.py
+++ b/passerelle/contrib/planitech/models.py
@@ -110,6 +110,66 @@ CREATE_RESERVATION_SCHEMA = {
 }
 
 
+GET_RESERVATION_PRICE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-03/schema#",
+    "title": "Planitech getreservationprice",
+    "description": "",
+    "type": "object",
+    "properties": {
+        "date": {
+            "description": "Date",
+            "type": "string",
+            "required": True
+        },
+        "start_time": {
+            "description": "Start time",
+            "type": "string",
+            "required": True
+        },
+        "end_time": {
+            "description": "End time",
+            "type": "string",
+            "required": True
+        },
+        "place_id": {
+            "description": "Place identifier",
+            "type": "number",
+            "required": True
+        },
+        "name_id": {
+            "description": "Publik user nameID",
+            "type": "string",
+            "required": True
+        },
+        "first_name": {
+            "description": "First name",
+            "type": "string",
+            "required": True
+        },
+        "last_name": {
+            "description": "Last name",
+            "type": "string",
+            "required": True
+        },
+        "email": {
+            "description": "Email",
+            "type": "string",
+            "required": True
+        },
+        "activity_id": {
+            "description": "Activity identifier",
+            "type": "number",
+            "required": True
+        },
+        "type_id": {
+            "description": "Rerservation type identifier",
+            "type": "number",
+            "required": True
+        }
+    }
+}
+
+
 RESERVATION_STATUS = {
     "confirmed": 3, "invalid": 0, " pre-reservation": 1,  "standard": 2
 }
@@ -322,6 +382,62 @@ class PlanitechConnector(BaseResource):
         except RequestException as e:
             raise APIError("Authentication to Planitech failed: %s" % str(e))
 
+    def get_or_create_pairing(self, post_data):
+        with transaction.atomic():
+            pairing, created = Pairing.objects.get_or_create(
+                resource=self, name_id=post_data['name_id'],
+                defaults={'external_id': uuid.uuid4().get_hex()})
+            if created:
+                params = {
+                    "externalUserIdentifier": pairing.external_id,
+                    "name": post_data['last_name'],
+                    "firstName": post_data['first_name'],
+                    "mail": post_data['email']
+                }
+                data = self._call_planitech(self.requests.post, 'createPerson', params)
+                if data.get('creationStatus') != 'OK':
+                    raise APIError("Person creation failed: %s" % data.get('creationStatus'))
+        return pairing
+
+    @endpoint(
+        perm='can_access',
+        post={
+            'description': _('Get reservation price'),
+            'request_body': {
+                'schema': {
+                    'application/json': GET_RESERVATION_PRICE_SCHEMA
+                }
+            }
+        }
+    )
+    def getreservationprice(self, request, post_data):
+        start_datetime = combine_date_time(post_data['date'], post_data['start_time'])
+        end_datetime = combine_date_time(post_data['date'], post_data['end_time'])
+
+        pairing = self.get_or_create_pairing(post_data)
+
+        params = {
+            "activityID": mste.Uint32(post_data['activity_id']),
+            "contractorExternalIdentifier": pairing.external_id,
+            "end": end_datetime,
+            "isWeekly": False,
+            "places": [float(post_data['place_id'])],
+            "start": start_datetime,
+            "typeID": mste.Uint32(post_data['type_id']),
+        }
+        data = self._call_planitech(self.requests.post, 'getFutureReservationPrice', params)
+        if data.get('calculationStatus') != 'OK':
+            raise APIError("Get reservation price failed: %s" % data.get('calculationStatus'))
+        price = data.get('calculatedPrice', False)
+        if price is False:
+            raise APIError("Get reservation price failed: no price")
+        return {
+            'data': {
+                'price': int(price),
+                'raw_data': data
+            }
+        }
+
     @endpoint(
         perm='can_access',
         post={
@@ -338,20 +454,7 @@ class PlanitechConnector(BaseResource):
         end_datetime = combine_date_time(post_data['date'], post_data['end_time'])
         request_date = datetime.now()
 
-        with transaction.atomic():
-            pairing, created = Pairing.objects.get_or_create(
-                resource=self, name_id=post_data['name_id'],
-                defaults={'external_id': uuid.uuid4().get_hex()})
-            if created:
-                params = {
-                    "externalUserIdentifier": pairing.external_id,
-                    "name": post_data['last_name'],
-                    "firstName": post_data['first_name'],
-                    "mail": post_data['email']
-                }
-                data = self._call_planitech(self.requests.post, 'createPerson', params)
-                if data.get('creationStatus') != 'OK':
-                    raise APIError("Person creation failed: %s" % data.get('creationStatus'))
+        pairing = self.get_or_create_pairing(post_data)
 
         params = {
             "activityID": mste.Uint32(post_data['activity_id']),
diff --git a/tests/test_planitech.py b/tests/test_planitech.py
index 1e50fc8..da4a953 100644
--- a/tests/test_planitech.py
+++ b/tests/test_planitech.py
@@ -233,6 +233,93 @@ def test_create_reservation(app, connector, monkeypatch):
     assert json_resp['err_desc'] == 'Reservation creation failed: no reservation ID'
 
 
+def test_get_reservation_price(app, connector, monkeypatch):
+    side_effect = [
+        {
+            'creationStatus': 'OK',
+            'externalUserIdentifier': '22b9c0d91fdc4f379d1356a4aaa9d38b',
+            'requestDate': datetime(2019, 1, 9, 15, 41),
+            'requestName': 'createPerson',
+            'responseDate': datetime(2019, 1, 9, 15, 41),
+            'userIdentifier': 13826.0
+        },
+        {
+            'calculationStatus': 'OK',
+            'calculatedPrice': 10
+        }
+    ]
+    mock_call_planitech = mock_planitech(monkeypatch, side_effect=side_effect)
+    params = {
+        'date': '2018-11-11',
+        'start_time': '10:00',
+        'end_time': '11:00',
+        'place_id': 1,
+        'name_id': 'john-doe',
+        'first_name': 'jon',
+        'last_name': 'doe',
+        'email': 'jon.doe@localhost',
+        'activity_id': 1,
+        'type_id': 1,
+    }
+
+    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
+    json_resp = response.json
+    assert json_resp['err'] == 0
+    assert json_resp['data']['price'] == 10
+
+    person_args = mock_call_planitech.call_args_list[0][0]
+    assert person_args[1] == 'createPerson'
+    person_args = person_args[2]
+    assert person_args['mail'] == 'jon.doe@localhost'
+    assert person_args['firstName'] == 'jon'
+    assert person_args['name'] == 'doe'
+    external_id = person_args['externalUserIdentifier']
+    pairing = Pairing.objects.get(resource=connector, name_id='john-doe')
+    assert pairing.external_id == external_id
+
+    reservation_args = mock_call_planitech.call_args_list[1][0]
+    assert reservation_args[1] == 'getFutureReservationPrice'
+    reservation_args = reservation_args[2]
+    assert reservation_args['start'] == datetime(2018, 11, 11, 10, 0)
+    assert reservation_args['end'] == datetime(2018, 11, 11, 11, 0)
+    assert reservation_args['places'] == [1]
+    assert reservation_args['contractorExternalIdentifier'] == pairing.external_id
+    assert reservation_args['activityID'] == 1
+    assert reservation_args['typeID'] == 1
+
+    # Second call for same user : no planitech person created
+    mock_call_planitech = mock_planitech(monkeypatch, return_value=side_effect[1])
+    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
+    json_resp = response.json
+    assert json_resp['err'] == 0
+    assert json_resp['data']['price'] == 10
+
+    reservation_args = mock_call_planitech.call_args_list[0][0]
+    assert reservation_args[1] == 'getFutureReservationPrice'
+    assert Pairing.objects.count() == 1
+
+    # Failure
+    mock_call_planitech = mock_planitech(
+        monkeypatch, return_value={
+            'calculationStatus': 'NOTOK',
+            'calculatedPrice': 10
+        })
+    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
+    json_resp = response.json
+    assert json_resp['err'] == 1
+    assert json_resp['err_desc'] == 'Get reservation price failed: NOTOK'
+
+    # Create reservation failed - nor reservation ID
+    mock_call_planitech = mock_planitech(
+        monkeypatch, return_value={
+            'calculationStatus': 'OK'
+        })
+    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
+    json_resp = response.json
+    assert json_resp['err'] == 1
+    assert json_resp['err_desc'] == 'Get reservation price failed: no price'
+
+
 def test_getplaces_referential(app, connector, monkeypatch):
     side_effect = [
         {
-- 
2.20.1

