From 2afb7f038b8876cde341519d6cc3b950fd622873 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 18 Jun 2021 09:43:54 +0200
Subject: [PATCH] caluire-axel: set_activity_agenda_typical_week endpoint
 (#54383)

---
 passerelle/contrib/caluire_axel/models.py  |  88 +++++++
 passerelle/contrib/caluire_axel/schemas.py |  32 +++
 tests/test_caluire_axel.py                 | 254 +++++++++++++++++++++
 3 files changed, 374 insertions(+)

diff --git a/passerelle/contrib/caluire_axel/models.py b/passerelle/contrib/caluire_axel/models.py
index 3883c24a..eaa198f0 100644
--- a/passerelle/contrib/caluire_axel/models.py
+++ b/passerelle/contrib/caluire_axel/models.py
@@ -34,6 +34,16 @@ from passerelle.utils.jsonresponse import APIError
 
 from . import schemas, utils
 
+WEEKDAYS = {
+    0: 'monday',
+    1: 'tuesday',
+    2: 'wednesday',
+    3: 'thursday',
+    4: 'friday',
+    5: 'saturday',
+    6: 'sunday',
+}
+
 
 class CaluireAxel(BaseResource):
 
@@ -811,6 +821,84 @@ class CaluireAxel(BaseResource):
             'count': updated,
         }
 
+    @endpoint(
+        display_category=_('Schooling'),
+        display_order=7,
+        description=_("Set activity agenda for a child with a typical week"),
+        perm='can_access',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+        },
+        post={
+            'request_body': {
+                'schema': {
+                    'application/json': schemas.TYPICAL_WEEK_BOOKING_SCHEMA,
+                }
+            }
+        },
+    )
+    def set_activity_agenda_typical_week(self, request, NameID, post_data):
+        link = self.get_link(NameID)
+        child_id = post_data['child_id']
+        child_data = self.get_child_data(link.family_id, child_id)
+        if child_data is None:
+            raise APIError('Child not found', err_code='not-found')
+        start_date, end_date, reference_year = self.get_start_and_end_dates(
+            post_data['start_date'], post_data['end_date']
+        )
+        activity_id = post_data['activity_id']
+        activity_data = self.get_child_activity(child_id, activity_id, reference_year)
+        if activity_data is None:
+            raise APIError('Activity not found', err_code='not-found')
+
+        if activity_id.startswith('CJ'):
+            raise APIError('Not available for this activity', err_code='bad-request', http_status=400)
+
+        booking_date = start_date
+        bookings = []
+        while booking_date <= end_date:
+            if WEEKDAYS[booking_date.weekday()] in post_data['booking_list']:
+                bookings.append(
+                    {
+                        'JOURDATE': booking_date.strftime(axel.json_date_format),
+                        'MATIN': 'X',
+                        'MIDI': None,
+                        'APRESMIDI': None,
+                    }
+                )
+            booking_date += datetime.timedelta(days=1)
+
+        if bookings:
+            # don't call axel if no changes
+            try:
+                data = {
+                    'PORTAIL': {
+                        'SETAGENDA': {
+                            'IDENTINDIVIDU': child_id,
+                            'ACTIVITE': {
+                                'IDENTACTIVITE': activity_id,
+                                'JOUR': bookings,
+                            },
+                        }
+                    }
+                }
+                result = schemas.set_agenda(self, data)
+            except axel.AxelError as e:
+                raise APIError(
+                    'Axel error: %s' % e,
+                    err_code='error',
+                    data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
+                )
+
+            code = result.json_response['DATA']['PORTAIL']['SETAGENDA']['CODE']
+            if code != 0:
+                raise APIError('Wrong agenda status', err_code='agenda-code-error-%s' % code)
+
+        return {
+            'updated': True,
+            'count': len(bookings),
+        }
+
     def get_invoices(self, regie_id, family_id):
         try:
             result = schemas.get_factures_a_payer(
diff --git a/passerelle/contrib/caluire_axel/schemas.py b/passerelle/contrib/caluire_axel/schemas.py
index 48ee80a4..42b173ef 100644
--- a/passerelle/contrib/caluire_axel/schemas.py
+++ b/passerelle/contrib/caluire_axel/schemas.py
@@ -147,6 +147,38 @@ BOOKING_SCHEMA = {
     ],
 }
 
+TYPICAL_WEEK_BOOKING_SCHEMA = {
+    'type': 'object',
+    'properties': {
+        'child_id': {
+            'type': 'string',
+            'minLength': 1,
+            'maxLength': 8,
+        },
+        'activity_id': {
+            'type': 'string',
+            'minLength': 1,
+            'maxLength': 10,
+        },
+        'booking_list': {
+            'type': 'array',
+            'items': {
+                'type': 'string',
+                'pattern': '(monday|tuesday|thursday|friday)',
+            },
+        },
+        'start_date': copy.deepcopy(axel.date_type),
+        'end_date': copy.deepcopy(axel.date_type),
+    },
+    'required': [
+        'child_id',
+        'activity_id',
+        'booking_list',
+        'start_date',
+        'end_date',
+    ],
+}
+
 
 PAYMENT_SCHEMA = {
     'type': 'object',
diff --git a/tests/test_caluire_axel.py b/tests/test_caluire_axel.py
index eba2b88d..68ad8010 100644
--- a/tests/test_caluire_axel.py
+++ b/tests/test_caluire_axel.py
@@ -147,6 +147,17 @@ def booking_params():
     }
 
 
+@pytest.fixture
+def week_booking_params():
+    return {
+        'child_id': '50632',
+        'activity_id': 'ELEM',
+        'booking_list': [],
+        'start_date': '2020-09-01',
+        'end_date': '2021-08-31',
+    }
+
+
 @pytest.fixture
 def upload_attachments_params():
     return {
@@ -2020,6 +2031,249 @@ def test_set_agenda_endpoint_wrong_code(app, resource, family_data, activities,
     assert resp.json['err'] == 'agenda-code-error-%s' % code
 
 
+@freezegun.freeze_time('2020-09-01')
+def test_set_activity_agenda_typical_week_endpoint_axel_error(
+    app, resource, family_data, activities, week_booking_params
+):
+    week_booking_params['booking_list'] = ['monday']
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
+        operation.side_effect = AxelError('FooBar')
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+        )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        with mock.patch('passerelle.contrib.caluire_axel.schemas.get_list_activites') as operation:
+            operation.side_effect = AxelError('FooBar')
+            resp = app.post_json(
+                '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+            )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+    with mock.patch(
+        'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+        return_value=family_data,
+    ):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+            return_value=activities,
+        ):
+            with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                operation.side_effect = AxelError('FooBar')
+                resp = app.post_json(
+                    '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                    params=week_booking_params,
+                )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+
+def test_set_activity_agenda_typical_week_endpoint_no_result(app, resource, week_booking_params):
+    week_booking_params['child_id'] = 'zzz'
+    resp = app.post_json(
+        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+    )
+    assert resp.json['err_desc'] == "Person not found"
+    assert resp.json['err'] == 'not-found'
+
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+        )
+        assert resp.json['err_desc'] == "Child not found"
+        assert resp.json['err'] == 'not-found'
+
+
+def test_set_activity_agenda_typical_week_endpoint_date_error(app, resource, week_booking_params):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        week_booking_params['start_date'] = '2021-09-01'
+        week_booking_params['end_date'] = '2021-08-31'
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+            params=week_booking_params,
+            status=400,
+        )
+        assert resp.json['err_desc'] == "start_date should be before end_date"
+        assert resp.json['err'] == 'bad-request'
+
+        week_booking_params['end_date'] = '2022-09-01'
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+            params=week_booking_params,
+            status=400,
+        )
+        assert (
+            resp.json['err_desc'] == "start_date and end_date are in different reference year (2021 != 2022)"
+        )
+        assert resp.json['err'] == 'bad-request'
+
+
+def test_set_activity_agenda_typical_week_endpoint(
+    app, resource, family_data, activities, week_booking_params
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    week_booking_params['end_date'] = '2020-09-01'
+    with mock.patch(
+        'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+        return_value=family_data,
+    ):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+            return_value=activities,
+        ):
+            resp = app.post_json(
+                '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                params=week_booking_params,
+            )
+    assert resp.json['err'] == 0
+    assert resp.json['updated'] is True
+    assert resp.json['count'] == 0
+
+
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_periscolaire(
+    app, resource, family_data, week_booking_params
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    week_booking_params['end_date'] = '2020-09-30'
+    for activity_id in ['ACCMAT', 'GARDERIES', 'ETUDES', 'CANTINE', 'FOOBAR']:
+        activity = {
+            'ENTREE': '2020-09-02',
+            'SORTIE': '2021-08-31',
+            'IDENTACTIVITE': activity_id,
+            'LIBELLEACTIVITE': 'FOOBAR',
+        }
+        week_booking_params['activity_id'] = activity_id
+        week_booking_params['booking_list'] = ['thursday']
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value={'ACTIVITE': [activity]},
+            ):
+                with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                    operation.return_value = OperationResult(
+                        json_response={'DATA': {'PORTAIL': {'SETAGENDA': {'CODE': 0}}}},
+                        xml_request='',
+                        xml_response='',
+                    )
+                    resp = app.post_json(
+                        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                        params=week_booking_params,
+                    )
+    assert resp.json['err'] == 0
+    assert resp.json['updated'] is True
+    assert resp.json['count'] == 4
+    assert len(operation.call_args_list) == 1
+
+    assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['IDENTINDIVIDU'] == '50632'
+    assert (
+        operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['IDENTACTIVITE'] == activity_id
+    )
+    assert len(operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR']) == 4
+    assert (
+        operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['JOURDATE']
+        == '2020-09-03'
+    )
+    assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['MATIN'] == 'X'
+    assert (
+        operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][1]['JOURDATE']
+        == '2020-09-10'
+    )
+    assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][1]['MATIN'] == 'X'
+    assert (
+        operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][2]['JOURDATE']
+        == '2020-09-17'
+    )
+    assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][2]['MATIN'] == 'X'
+    assert (
+        operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][3]['JOURDATE']
+        == '2020-09-24'
+    )
+    assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][3]['MATIN'] == 'X'
+
+
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_extrascolaire(
+    app, resource, family_data, week_booking_params
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    week_booking_params['end_date'] = '2020-09-30'
+    for activity_id in ['CJ MER', 'CJ VAC']:
+        activity = {
+            'ENTREE': '2020-09-02',
+            'SORTIE': '2021-08-31',
+            'IDENTACTIVITE': activity_id,
+            'LIBELLEACTIVITE': 'FOOBAR',
+        }
+        week_booking_params['activity_id'] = activity_id
+        week_booking_params['booking_list'] = ['friday']
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value={'ACTIVITE': [activity]},
+            ):
+                resp = app.post_json(
+                    '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                    params=week_booking_params,
+                    status=400,
+                )
+    assert resp.json['err_desc'] == "Not available for this activity"
+    assert resp.json['err'] == 'bad-request'
+
+
+@pytest.mark.parametrize('code', [-1, -2, -3, -4])
+def test_set_activity_agenda_typical_week_endpoint_wrong_code(
+    app, resource, family_data, activities, week_booking_params, code
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    week_booking_params['end_date'] = '2020-09-30'
+    week_booking_params['booking_list'] = ['monday']
+    content = (
+        '''<PORTAIL>
+    <SETAGENDA>
+        <CODE>%s</CODE>
+    </SETAGENDA>
+</PORTAIL>'''
+        % code
+    )
+    with mock_data(content, 'SetAgenda', data_method='setData'):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value=activities,
+            ):
+                resp = app.post_json(
+                    '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                    params=week_booking_params,
+                )
+    assert resp.json['err_desc'] == "Wrong agenda status"
+    assert resp.json['err'] == 'agenda-code-error-%s' % code
+
+
 def test_invoices_endpoint_axel_error(app, resource):
     Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
     with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation:
-- 
2.30.2

