diff --git a/passerelle/contrib/toulouse_maelis/migrations/0002_referential.py b/passerelle/contrib/toulouse_maelis/migrations/0002_referential.py index 86dcae3e..40c530f5 100644 --- a/passerelle/contrib/toulouse_maelis/migrations/0002_referential.py +++ b/passerelle/contrib/toulouse_maelis/migrations/0002_referential.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.26 on 2022-12-08 16:05 import django.contrib.postgres.fields.jsonb +import django.core.serializers.json import django.db.models.deletion from django.db import migrations, models @@ -22,7 +23,12 @@ class Migration(migrations.Migration): ('referential_name', models.CharField(max_length=64, verbose_name='Name')), ('item_id', models.CharField(max_length=64, verbose_name='Key')), ('item_text', models.CharField(max_length=128, verbose_name='Text')), - ('item_data', django.contrib.postgres.fields.jsonb.JSONField(verbose_name='Data')), + ( + 'item_data', + django.contrib.postgres.fields.jsonb.JSONField( + encoder=django.core.serializers.json.DjangoJSONEncoder, verbose_name='Data' + ), + ), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ( diff --git a/passerelle/contrib/toulouse_maelis/migrations/0006_auto_20230324_1730.py b/passerelle/contrib/toulouse_maelis/migrations/0006_auto_20230324_1730.py new file mode 100644 index 00000000..aead6a7f --- /dev/null +++ b/passerelle/contrib/toulouse_maelis/migrations/0006_auto_20230324_1730.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.26 on 2023-03-24 16:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('toulouse_maelis', '0005_auto_20221221_1546'), + ] + + operations = [ + migrations.AddField( + model_name='toulousemaelis', + name='extrasco_nature_codes', + field=models.TextField( + blank=True, + default='X', + verbose_name='Codes des natures des activités extra-scolaires, séparés par des virgules', + ), + ), + migrations.AddField( + model_name='toulousemaelis', + name='loisir_nature_codes', + field=models.TextField( + blank=True, + default='P,L,S,1,2,3,4,5,6,7,8,9', + verbose_name='Codes des natures des activités loisirs, séparés par des virgules', + ), + ), + migrations.AddField( + model_name='toulousemaelis', + name='perisco_nature_codes', + field=models.TextField( + blank=True, + default='A,R', + verbose_name='Codes des natures des activités péri-scolaires, séparés par des virgules', + ), + ), + ] diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py index 93a40580..43a2af35 100644 --- a/passerelle/contrib/toulouse_maelis/models.py +++ b/passerelle/contrib/toulouse_maelis/models.py @@ -59,6 +59,21 @@ class ToulouseMaelis(BaseResource, HTTPResource): zeep_wsse_password = models.CharField( max_length=64, blank=True, default='', verbose_name='Mot de passe WSSE' ) + perisco_nature_codes = models.TextField( + blank=True, + default='A,R', + verbose_name='Codes des natures des activités péri-scolaires, séparés par des virgules', + ) + extrasco_nature_codes = models.TextField( + blank=True, + default='X', + verbose_name='Codes des natures des activités extra-scolaires, séparés par des virgules', + ) + loisir_nature_codes = models.TextField( + blank=True, + default='P,L,S,' + ','.join(str(x) for x in range(1, 10)), + verbose_name='Codes des natures des activités loisirs, séparés par des virgules', + ) category = 'Connecteurs métiers' _category_ordering = ['Famille', 'Activités'] @@ -66,6 +81,15 @@ class ToulouseMaelis(BaseResource, HTTPResource): class Meta: verbose_name = 'Toulouse Maelis' + def get_perisco_nature_codes(self): + return [x.strip() for x in self.perisco_nature_codes.split(',') if x.strip()] + + def get_extrasco_nature_codes(self): + return [x.strip() for x in self.extrasco_nature_codes.split(',') if x.strip()] + + def get_loisir_nature_codes(self): + return [x.strip() for x in self.loisir_nature_codes.split(',') if x.strip()] + def get_client(self, wsdl_short_name): wsse = UsernameToken(self.zeep_wsse_username, self.zeep_wsse_password) wsdl_name = wsdl_short_name + 'Service?wsdl' @@ -570,16 +594,23 @@ class ToulouseMaelis(BaseResource, HTTPResource): self, family_id, person_id, - nature_id=None, + nature=None, type_ids=None, reference_year=None, start_date=None, end_date=None, ): + if str(nature).lower() == 'extrasco': + nature_filter_codes = self.get_extrasco_nature_codes() + elif str(nature).lower() == 'loisir': + nature_filter_codes = self.get_loisir_nature_codes() + else: + nature_filter_codes = None + type_filter_codes = [x.strip() for x in str(type_ids or '').split(',') if x.strip()] + params = { 'numDossier': family_id, 'numPerson': person_id, - 'codeNatureActivity': nature_id, 'yearSchool': reference_year, 'dateStartActivity': start_date, 'dateEndActivity': end_date, @@ -587,11 +618,19 @@ class ToulouseMaelis(BaseResource, HTTPResource): data = self.call( 'Activity', 'getPersonCatalogueActivity', getPersonCatalogueActivityRequestBean=params ) - if type_ids: - codes = [x.strip() for x in type_ids.split(',') if x.strip()] - data['catalogueActivityList'] = [ - a for a in data['catalogueActivityList'] if a['activity']['activityType']['code'] in codes - ] + + activities = [] + for item in data['catalogueActivityList']: + activity_type = item['activity'].get('activityType') + activity_nature = activity_type.get('natureSpec') if activity_type else None + if type_filter_codes: + if not activity_type or activity_type['code'] not in type_filter_codes: + continue + if nature_filter_codes: + if not activity_nature or activity_nature['code'] not in nature_filter_codes: + continue + activities.append(item) + data['catalogueActivityList'] = activities return data def get_baskets_raw(self, family_id): @@ -1853,7 +1892,11 @@ class ToulouseMaelis(BaseResource, HTTPResource): activity = schedule['activity'] if not activity['activityType']['natureSpec']: continue - if activity['activityType']['natureSpec']['code'] not in ['A', 'R', 'X']: + if ( + activity['activityType']['natureSpec']['code'] not in self.get_perisco_nature_codes() + and activity['activityType']['natureSpec']['code'] + not in self.get_extrasco_nature_codes() + ): continue activity_id = activity['idAct'] many_units = len(schedule['unitScheduleList']) > 1 @@ -1869,7 +1912,8 @@ class ToulouseMaelis(BaseResource, HTTPResource): 'prefill': day['scheduledPresence'] > 0 or day['realPresence'] > 1, 'disabled': ( day['status'] != 'WRITABLE' - or activity['activityType']['natureSpec']['code'] in ['X'] + or activity['activityType']['natureSpec']['code'] + in self.get_extrasco_nature_codes() ), 'details': day, } @@ -1879,7 +1923,10 @@ class ToulouseMaelis(BaseResource, HTTPResource): booking['details']['status_color'] = color booking['details']['activity_id'] = activity_id booking['details']['activity_type'] = activity['activityType']['code'] - if activity['activityType']['natureSpec']['code'] in ['A', 'R']: + if ( + activity['activityType']['natureSpec']['code'] + in self.get_perisco_nature_codes() + ): booking['details']['activity_label'] = activity['activityType']['libelle'] else: booking['details']['activity_label'] = ( @@ -2270,19 +2317,10 @@ class ToulouseMaelis(BaseResource, HTTPResource): 'description': "Date de référence, utilisée pour déduire l'année scolaire", 'type': 'date', }, - 'nature_ids': { - 'description': "Codes des natures des activités (par défaut les activités loisirs), séparées par des virgules", - 'example_value': 'P,1,2', - }, }, ) - def read_activity_list(self, request, ref_date, nature_ids=None): + def read_activity_list(self, request, ref_date): reference_year = utils.get_reference_year_from_date(ref_date) - if not nature_ids: - # actual loisir nature codes - nature_filter_codes = ['P', 'L', 'S'] + [str(i) for i in range(1, 10)] - else: - nature_filter_codes = [x.strip() for x in nature_ids.split(',') if x.strip()] labels = { 'nature': "Nature de l'activité", 'type': "Type de l'activité", @@ -2316,7 +2354,7 @@ class ToulouseMaelis(BaseResource, HTTPResource): for activity in activities: activity_type = activity['activityPortail'].get('activityType') activity_nature = activity_type.get('natureSpec') if activity_type else None - if not activity_nature or activity_nature['code'] not in nature_filter_codes: + if not activity_nature or activity_nature['code'] not in self.get_loisir_nature_codes(): continue activity['id'] = activity['activityPortail']['idAct'] activity['text'] = activity['activityPortail']['libelle'] @@ -2378,7 +2416,9 @@ class ToulouseMaelis(BaseResource, HTTPResource): perm='can_access', parameters={ 'person_id': {'description': "Numéro du responsale légal ou de l'enfant"}, - 'nature_id': {'description': "Numéro de la nature des activités"}, + 'nature': { + 'description': "Nature des activités : EXTRASCO ou LOISIR (toutes par défaut)", + }, 'type_ids': { 'description': "Codes des types des activités, séparées par des virgules", 'example_value': 'EXTMERC,EXTVAC', @@ -2397,7 +2437,7 @@ class ToulouseMaelis(BaseResource, HTTPResource): person_id, NameID=None, family_id=None, - nature_id=None, + nature=None, type_ids=None, start_date=None, end_date=None, @@ -2413,12 +2453,13 @@ class ToulouseMaelis(BaseResource, HTTPResource): response = self.get_person_activity_list_raw( family_id, person_id, - nature_id=nature_id, + nature=nature, type_ids=type_ids, reference_year=reference_year, start_date=start_date and start_date.strftime(utils.json_date_format), end_date=start_date and end_date.strftime(utils.json_date_format), ) + for item in response['catalogueActivityList']: item['id'] = item['activity']['idActivity'] item['text'] = render_to_string(text_template, item).strip() @@ -2555,7 +2596,9 @@ class ToulouseMaelis(BaseResource, HTTPResource): 'NameID': {'description': 'Publik NameID'}, 'family_id': {'description': 'Numéro de DUI'}, 'person_id': {'description': "Numéro du responsale légal ou de l'enfant"}, - 'nature_id': {'description': "Numéro de la nature des activités"}, + 'nature': { + 'description': "Nature des activités : EXTRASCO ou LOISIR (toutes par défaut)", + }, 'type_ids': { 'description': "Codes des types des activités, séparées par des virgules", 'example_value': 'EXTMERC,EXTVAC', @@ -2575,7 +2618,7 @@ class ToulouseMaelis(BaseResource, HTTPResource): family_id=None, start_date=None, end_date=None, - nature_id=None, + nature=None, type_ids=None, activity_id=None, unit_id=None, @@ -2589,7 +2632,7 @@ class ToulouseMaelis(BaseResource, HTTPResource): response = self.get_person_activity_list_raw( family_id, person_id, - nature_id=nature_id, + nature=nature, type_ids=type_ids, reference_year=reference_year, start_date=start_date and start_date.strftime(utils.json_date_format), diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py index 1e8b9990..3f7fd119 100644 --- a/tests/test_toulouse_maelis.py +++ b/tests/test_toulouse_maelis.py @@ -32,6 +32,7 @@ from passerelle.contrib.toulouse_maelis.utils import get_public_criterias, json_ from passerelle.utils.jsonresponse import APIError from passerelle.utils.soap import SOAPError from passerelle.utils.templates import render_to_string +from tests.test_manager import login from tests.utils import FakedResponse, ResponsesSoap, generic_endpoint_url, setup_access_rights TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'toulouse_maelis') @@ -243,6 +244,63 @@ def con(db): return ToulouseMaelis.objects.get() +def test_nature_codes(con): + assert con.get_perisco_nature_codes() == ['A', 'R'] + assert con.get_extrasco_nature_codes() == ['X'] + assert con.get_loisir_nature_codes() == ['P', 'L', 'S', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + + +def test_manager(admin_user, app, con): + app = login(app) + path = '/%s/%s/' % (con.get_connector_slug(), con.slug) + resp = app.get(path) + assert [ + x.text + for x in resp.html.find('div', {'id': 'description'}).find_all('p') + if x.text.startswith('Codes des natures des activités péri-scolaires') + ][0].split(':')[1].strip() == 'A,R' + assert [ + x.text + for x in resp.html.find('div', {'id': 'description'}).find_all('p') + if x.text.startswith('Codes des natures des activités extra-scolaires') + ][0].split(':')[1].strip() == 'X' + assert ( + 'P,L,S,1,2,3' + in [ + x.text + for x in resp.html.find('div', {'id': 'description'}).find_all('p') + if x.text.startswith('Codes des natures des activités loisirs') + ][0] + ) + + path = '/manage/%s/%s/edit' % (con.get_connector_slug(), con.slug) + resp = app.get(path) + resp.form['title'] = 'Malis connector' + resp.form['description'] = 'Malis connector' + resp.form['perisco_nature_codes'] = 'P,L, O ,P' + resp.form['extrasco_nature_codes'] = 'Z' + resp.form['loisir_nature_codes'] = '' + resp = resp.form.submit() + resp = resp.follow() + assert [ + x.text + for x in resp.html.find('div', {'id': 'description'}).find_all('p') + if x.text.startswith('Codes des natures des activités péri-scolaires') + ][0].split(':')[1].strip() == 'P,L, O ,P' + assert [ + x.text + for x in resp.html.find('div', {'id': 'description'}).find_all('p') + if x.text.startswith('Codes des natures des activités extra-scolaires') + ][0].split(':')[1].strip() == 'Z' + assert 'Codes des natures des activités loisirs' not in [ + x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p') + ] + con = ToulouseMaelis.objects.get() + assert con.get_perisco_nature_codes() == ['P', 'L', 'O', 'P'] + assert con.get_extrasco_nature_codes() == ['Z'] + assert con.get_loisir_nature_codes() == [] + + @mock.patch('passerelle.utils.Request.get') def test_call_with_wrong_wsdl_url(mocked_get, con): mocked_get.side_effect = CONNECTION_ERROR @@ -5084,9 +5142,10 @@ def test_read_activity_list(con, app, freezer): freezer.move_to('2023-01-01 12:00') url = get_endpoint('read-activity-list') + con.loisir_nature_codes = '4,L,, S ' + con.save() params = { 'ref_date': datetime.date.today().strftime(json_date_format), - 'nature_ids': '4,L,, S ', } resp = app.get(url, params=params) assert resp.json['err'] == 0 @@ -5126,12 +5185,14 @@ def test_read_activity_list(con, app, freezer): }, } - params['nature_ids'] = 'X,L,S' + con.loisir_nature_codes = 'X,L,S' + con.save() resp = app.get(url, params=params) assert resp.json['err'] == 0 assert len(resp.json['data']) == 0 - params['nature_ids'] = '' + con.loisir_nature_codes = '4,L,, S ' + con.save() resp = app.get(url, params=params) assert resp.json['err'] == 0 assert len(resp.json['data']) == 4 @@ -5188,11 +5249,13 @@ def test_get_person_activity_list(activity_service, con, app): ) url = get_endpoint('get-person-activity-list') + con.extrasco_nature_codes = 'V' + con.save() params = { 'NameID': '', 'family_id': '311323', 'person_id': '246423', - 'nature_id': '', + 'nature': '', 'start_date': '2022-09-01', 'end_date': '2023-08-31', 'text_template': '', @@ -5286,6 +5349,22 @@ def test_get_person_activity_list(activity_service, con, app): ('A10053187065', 'Vacances Hivers 2023'), ] + params['type_ids'] = '' + params['nature'] = 'LOISIR' + resp = app.get(url, params=params) + assert resp.json['err'] == 0 + assert [(x['id'], x['text']) for x in resp.json['data']] == [ + ('A10051141965', 'Activité modèle'), + ] + + params['nature'] = 'EXTRASCO' + resp = app.get(url, params=params) + assert resp.json['err'] == 0 + assert [(x['id'], x['text']) for x in resp.json['data']] == [ + ('A10053187087', 'Vacances Ete 2023'), + ('A10053187065', 'Vacances Hivers 2023'), + ] + def test_get_person_activity_list_not_linked_error(con, app): url = get_endpoint('get-person-activity-list') @@ -5293,7 +5372,7 @@ def test_get_person_activity_list_not_linked_error(con, app): 'NameID': 'local', 'family_id': '', 'person_id': '246423', - 'nature_id': '', + 'nature': '', 'start_date': '2022-09-01', 'end_date': '2023-08-31', 'text_template': '', @@ -5310,7 +5389,7 @@ def test_get_person_activity_list_date_error(con, app): 'NameID': '', 'family_id': '311323', 'person_id': '246423', - 'nature_id': '', + 'nature': '', 'start_date': 'bad', 'end_date': '2023-08-31', 'text_template': '', @@ -5643,11 +5722,13 @@ def test_get_person_catalog_geojson(activity_service, con, app): ) url = get_endpoint('get-person-catalog-geojson') + con.extrasco_nature_codes = 'V' + con.save() params = { 'NameID': '', 'family_id': '311323', 'person_id': '246423', - 'nature_id': '', + 'nature': '', 'start_date': '2022-09-01', 'end_date': '2023-08-31', 'activity_id': 'A10053187087', @@ -5767,6 +5848,19 @@ def test_get_person_catalog_geojson(activity_service, con, app): resp = app.get(url, params=params) assert len(resp.json['features']) == 0 + params['type_ids'] = '' + params['nature'] = 'EXTRASCO' + resp = app.get(url, params=params) + assert len(resp.json['features']) == 4 + assert [ + x['properties']['activity']['activity']['activityType']['natureSpec']['code'] + for x in resp.json['features'] + ] == ['V', 'V', 'V', 'V'] + + params['nature'] = 'LOISIR' + resp = app.get(url, params=params) + assert len(resp.json['features']) == 0 # no lon/lat + def test_get_person_catalog_geojson_not_linked_error(con, app): url = get_endpoint('get-person-catalog-geojson')