From b6b40e501b2a27659ec57e72f6bdd296568117a9 Mon Sep 17 00:00:00 2001 From: Nicolas ROCHE Date: Tue, 12 Jul 2022 18:54:14 +0200 Subject: [PATCH] toulouse_maelis: parsifal: add referentials webservices (#67325) --- passerelle/contrib/toulouse_maelis/models.py | 132 +++++++++++ .../toulouse_maelis/R_read_category_list.xml | 19 ++ .../toulouse_maelis/R_read_civility_list.xml | 15 ++ .../data/toulouse_maelis/R_read_csp_list.xml | 179 +++++++++++++++ .../toulouse_maelis/R_read_quality_list.xml | 75 ++++++ .../toulouse_maelis/R_read_situation_list.xml | 43 ++++ tests/test_toulouse_maelis.py | 217 +++++++++++++++++- 7 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 tests/data/toulouse_maelis/R_read_category_list.xml create mode 100644 tests/data/toulouse_maelis/R_read_civility_list.xml create mode 100644 tests/data/toulouse_maelis/R_read_csp_list.xml create mode 100644 tests/data/toulouse_maelis/R_read_quality_list.xml create mode 100644 tests/data/toulouse_maelis/R_read_situation_list.xml diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py index 9a30ea73..dc7a591f 100644 --- a/passerelle/contrib/toulouse_maelis/models.py +++ b/passerelle/contrib/toulouse_maelis/models.py @@ -17,6 +17,7 @@ from urllib.parse import urljoin import zeep +from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ from zeep.helpers import serialize_object @@ -68,6 +69,44 @@ class ToulouseMaelis(BaseResource, HTTPResource): assert self.call('Family', 'isWSRunning') assert self.call('Activity', 'isWSRunning') + def get_referential(self, referential_name): + + # local referentials + if referential_name == 'Complement': + response = [ + {'id': 'B', 'text': 'bis'}, + {'id': 'T', 'text': 'ter'}, + {'id': 'Q', 'text': 'quater'}, + ] + return {'list': response, 'dict': {x['id']: x['text'] for x in response}} + elif referential_name == 'Sex': + response = [ + {'id': 'M', 'text': 'Masculin'}, + {'id': 'F', 'text': 'Féminin'}, + ] + return {'list': response, 'dict': {x['id']: x['text'] for x in response}} + + # remote referentials + cache_key = 'maelis-%s-%s' % (self.pk, referential_name) + data = cache.get(cache_key) + if data is None: + response = self.call('Family', 'read' + referential_name + 'List') + data = { + 'list': [{'id': x.code, 'text': x.libelle} for x in response], + 'dict': {x.code: x.libelle for x in response}, + } + # put in cache for two hours + cache.set(cache_key, data, 3600 * 2) + return data + + def get_referential_value(self, referential_name, key): + try: + return self.get_referential(referential_name)['dict'][key] + except KeyError: + # Maelis DB not properly configurated + self.logger.warning("No '%s' key into Maelis '%s' referential", key, referential_name) + return key + def get_link(self, NameID): try: return self.link_set.get(name_id=NameID) @@ -77,8 +116,101 @@ class ToulouseMaelis(BaseResource, HTTPResource): def get_family(self, family_id): response = self.call('Family', 'readFamily', dossierNumber=family_id) data = serialize_object(response) + + def add_text_value(referential_name, data, keys): + last_key = keys.pop() + for key in keys: + if not isinstance(data, dict) or not key in data: + return + data = data[key] + if isinstance(data, dict) and last_key in data and data[last_key] is not None: + data[last_key + '_text'] = self.get_referential_value(referential_name, data[last_key]) + + # add text from referentials + add_text_value('Category', data, ['category']) + add_text_value('Situation', data, ['situation']) + for rlg in 'RL1', 'RL2': + add_text_value('Civility', data, [rlg, 'civility']) + add_text_value('Quality', data, [rlg, 'quality']) + add_text_value('Complement', data, [rlg, 'adresse', 'numComp']) + add_text_value('CSP', data, [rlg, 'profession', 'codeCSP']) + for child in data['childList']: + add_text_value('Sex', child, ['sexe']) return data + @endpoint( + display_category=_('Family'), + description='Liste des catégories', + name='read-category-list', + perm='can_access', + ) + def read_category_list(self, request): + return {'data': self.get_referential('Category')['list']} + + @endpoint( + display_category=_('Family'), + description='Liste des civilités', + name='read-civility-list', + perm='can_access', + ) + def read_civility_list(self, request): + return {'data': self.get_referential('Civility')['list']} + + @endpoint( + display_category=_('Family'), + description='Liste des compléments du numéro de voie', + name='read-complement-list', + perm='can_access', + ) + def read_complement_list(self, request): + return {'data': self.get_referential('Complement')['list']} + + @endpoint( + display_category=_('Family'), + description='liste des catégories socio-professionnelles', + name='read-csp-list', + perm='can_access', + ) + def read_csp_list(self, request): + data = self.get_referential('CSP')['list'] + + # remove redundant codes + uniq_text = set() + uniq_data = [] + for item in data: + item['text'] = item['text'].strip() + if item['text'] not in uniq_text: + uniq_data.append(item) + uniq_text.add(item['text']) + return {'data': uniq_data} + + @endpoint( + display_category=_('Family'), + description='liste des qualités du référenciel', + name='read-quality-list', + perm='can_access', + ) + def read_quality_list(self, request): + return {'data': self.get_referential('Quality')['list']} + + @endpoint( + display_category=_('Family'), + description='Liste des sexes', + name='read-sex-list', + perm='can_access', + ) + def read_sex_list(self, request): + return {'data': self.get_referential('Sex')['list']} + + @endpoint( + display_category=_('Family'), + description='liste des situations', + name='read-situation-list', + perm='can_access', + ) + def read_situation_list(self, request): + return {'data': self.get_referential('Situation')['list']} + @endpoint( display_category=_('Family'), description=_('Create link between user and family'), diff --git a/tests/data/toulouse_maelis/R_read_category_list.xml b/tests/data/toulouse_maelis/R_read_category_list.xml new file mode 100644 index 00000000..19d2d933 --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_category_list.xml @@ -0,0 +1,19 @@ + + + + + + BI + BIPARENTALE + + + ACCEUI + FAMILLE D'ACCUEIL + + + MONO + MONOPARENTALE + + + + diff --git a/tests/data/toulouse_maelis/R_read_civility_list.xml b/tests/data/toulouse_maelis/R_read_civility_list.xml new file mode 100644 index 00000000..012c222d --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_civility_list.xml @@ -0,0 +1,15 @@ + + + + + + MME + Madame + + + M. + Monsieur + + + + diff --git a/tests/data/toulouse_maelis/R_read_csp_list.xml b/tests/data/toulouse_maelis/R_read_csp_list.xml new file mode 100644 index 00000000..e389e734 --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_csp_list.xml @@ -0,0 +1,179 @@ + + + + + + 14 + AGENT DE MAITRISE + + + 1 + AGRICULTEUR + + + AGR + AGRICULTEUR + + + ARTI + ARTISAN + + + ART + ARTISAN + + + 2 + ARTISAN-COMMERCANT + + + 15 + AUTRES + + + CADR + CADRE + + + 4 + CADRE + + + 13 + CADRE SUPERIEUR + + + 3 + CHEF D'ENTREPRISE + + + CHOM + CHOMEUR + + + COM + COMMERCANT + + + COMM + COMMERCANT + + + 7 + COMMERCANT + + + 10 + DEMANDEUR D'EMPLOI + + + DIV + DIVERS + + + EMP + EMPLOYE + + + 5 + EMPLOYE + + + ENS + ENSEIGNANT + + + EN + ENSEIGNANT + + + 17 + ENSEIGNANT + + + ETU + ETUDIANT + + + 8 + ETUDIANT + + + 11 + FONCTIONNAIRE + + + FONC + FONCTIONNAIRE + + + FONCT + FONCTIONNAIRE + + + MAIR + MAIRIE DE NICE + + + OUV + OUVRIER + + + 6 + OUVRIER + + + PERENS + PERISCO ENSEIGNANT + + + PEREXT + PERISCO EXTERNE + + + PERMAI + PERISCO MAIRIE DE NICE + + + PERANI + PERISCO S.ANIMATION + + + 9 + PROFESSION LIBERALE + + + LIB + PROFESSION LIBERALE + + + 12 + RETRAITE + + + RET + RETRAITE + + + RMI + REVENU MINIMUM D'INSERTION + + + 16 + SANS PROFESSION + + + SANPRO + SANS PROFESSION + + + SANS + SANS PROFESSION + + + SEC + SECRETAIRE + + + + diff --git a/tests/data/toulouse_maelis/R_read_quality_list.xml b/tests/data/toulouse_maelis/R_read_quality_list.xml new file mode 100644 index 00000000..f86e3147 --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_quality_list.xml @@ -0,0 +1,75 @@ + + + + + + AU + AUTRE + + + BP + BEAU PERE + + + BM + BELLE MERE + + + CONSO + CONSOMMATEUR + + + EN + ENFANT + + + FS + FRERES ET SOEURS + + + GM + GRAND MERE MATERNELLE + + + GP + GRAND PERE MATERNEL + + + GMP + GRAND-MERE PATERNELLE + + + GPP + GRAND-PERE PATERNEL + + + MAIRIE + MAIRIE + + + MERE + MERE + + + O + ONCLE + + + OS + ORGANISME SOCIAL + + + PERE + PERE + + + T + TANTE + + + TUTEUR + TUTEUR + + + + diff --git a/tests/data/toulouse_maelis/R_read_situation_list.xml b/tests/data/toulouse_maelis/R_read_situation_list.xml new file mode 100644 index 00000000..cb3f7d58 --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_situation_list.xml @@ -0,0 +1,43 @@ + + + + + + C + Célibataire + + + D + Divorcé (e) + + + CS + EN COURS DE SEPARATION + + + M + Marié (e) + + + P + Pacsé (e) + + + S + Séparé (e) + + + UL + UNION LIBRE + + + V + Veuf (ve) + + + VM + Vivant maritalement + + + + diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py index ed16a1b6..feb73ff9 100644 --- a/tests/test_toulouse_maelis.py +++ b/tests/test_toulouse_maelis.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging import os import mock @@ -40,6 +41,11 @@ FAILED_AUTH = FakedResponse(content=get_xml_file('R_failed_authentication.xml'), ISWSRUNNING_TRUE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'true', status_code=200) ISWSRUNNING_FALSE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'false', status_code=200) READ_FAMILY = FakedResponse(content=get_xml_file('R_read_family.xml'), status_code=200) +READ_CATEGORIES = FakedResponse(content=get_xml_file('R_read_category_list.xml'), status_code=200) +READ_CIVILITIES = FakedResponse(content=get_xml_file('R_read_civility_list.xml'), status_code=200) +READ_CSP = FakedResponse(content=get_xml_file('R_read_csp_list.xml'), status_code=200) +READ_QUALITIES = FakedResponse(content=get_xml_file('R_read_quality_list.xml'), status_code=200) +READ_SITUATIONS = FakedResponse(content=get_xml_file('R_read_situation_list.xml'), status_code=200) def get_endpoint(name): @@ -160,11 +166,197 @@ def test_unlink(con, app): assert resp.json['err_desc'] == 'User not linked to family' +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_get_referential_using_cache(mocked_post, mocked_get, con, freezer): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CATEGORIES + + freezer.move_to('2021-11-10 00:00') + con.get_referential('Category') + assert mocked_post.call_count == 1 + + con.get_referential('Category') + assert mocked_post.call_count == 1 + + freezer.move_to('2021-11-10 02:00') + con.get_referential('Category') + assert mocked_post.call_count == 2 + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_get_referential_value(mocked_post, mocked_get, con): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CSP + + assert con.get_referential_value('CSP', '1') == 'AGRICULTEUR' + assert con.get_referential_value('CSP', 'AGR') == 'AGRICULTEUR' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_get_referential_value_not_found(mocked_post, mocked_get, con, caplog): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CIVILITIES + + assert con.get_referential_value('Civility', 'MR') == 'MR' + assert len(caplog.records) == 1 + assert caplog.records[0].levelno == logging.WARNING + assert caplog.records[0].message == "No 'MR' key into Maelis 'Civility' referential" + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_category_list(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CATEGORIES + url = get_endpoint('read-category-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': 'BI', 'text': 'BIPARENTALE'}, + {'id': 'ACCEUI', 'text': "FAMILLE D'ACCUEIL"}, + {'id': 'MONO', 'text': 'MONOPARENTALE'}, + ] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_civility_list(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CIVILITIES + url = get_endpoint('read-civility-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {"id": "MME", "text": "Madame"}, + {"id": "M.", "text": "Monsieur"}, + ] + + +def test_read_complement_list(con, app): + url = get_endpoint('read-complement-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': 'B', 'text': 'bis'}, + {'id': 'T', 'text': 'ter'}, + {'id': 'Q', 'text': 'quater'}, + ] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_csp_list(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_CSP + url = get_endpoint('read-csp-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': '14', 'text': 'AGENT DE MAITRISE'}, + {'id': '1', 'text': 'AGRICULTEUR'}, + {'id': 'ARTI', 'text': 'ARTISAN'}, + {'id': '2', 'text': 'ARTISAN-COMMERCANT'}, + {'id': '15', 'text': 'AUTRES'}, + {'id': 'CADR', 'text': 'CADRE'}, + {'id': '13', 'text': 'CADRE SUPERIEUR'}, + {'id': '3', 'text': "CHEF D'ENTREPRISE"}, + {'id': 'CHOM', 'text': 'CHOMEUR'}, + {'id': 'COM', 'text': 'COMMERCANT'}, + {'id': '10', 'text': "DEMANDEUR D'EMPLOI"}, + {'id': 'DIV', 'text': 'DIVERS'}, + {'id': 'EMP', 'text': 'EMPLOYE'}, + {'id': 'ENS', 'text': 'ENSEIGNANT'}, + {'id': 'ETU', 'text': 'ETUDIANT'}, + {'id': '11', 'text': 'FONCTIONNAIRE'}, + {'id': 'MAIR', 'text': 'MAIRIE DE NICE'}, + {'id': 'OUV', 'text': 'OUVRIER'}, + {'id': 'PERENS', 'text': 'PERISCO ENSEIGNANT'}, + {'id': 'PEREXT', 'text': 'PERISCO EXTERNE'}, + {'id': 'PERMAI', 'text': 'PERISCO MAIRIE DE NICE'}, + {'id': 'PERANI', 'text': 'PERISCO S.ANIMATION'}, + {'id': '9', 'text': 'PROFESSION LIBERALE'}, + {'id': '12', 'text': 'RETRAITE'}, + {'id': 'RMI', 'text': "REVENU MINIMUM D'INSERTION"}, + {'id': '16', 'text': 'SANS PROFESSION'}, + {'id': 'SEC', 'text': 'SECRETAIRE'}, + ] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_quality_list(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_QUALITIES + url = get_endpoint('read-quality-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': 'AU', 'text': 'AUTRE'}, + {'id': 'BP', 'text': 'BEAU PERE'}, + {'id': 'BM', 'text': 'BELLE MERE'}, + {'id': 'CONSO', 'text': 'CONSOMMATEUR'}, + {'id': 'EN', 'text': 'ENFANT'}, + {'id': 'FS', 'text': 'FRERES ET SOEURS'}, + {'id': 'GM', 'text': 'GRAND MERE MATERNELLE'}, + {'id': 'GP', 'text': 'GRAND PERE MATERNEL'}, + {'id': 'GMP', 'text': 'GRAND-MERE PATERNELLE'}, + {'id': 'GPP', 'text': 'GRAND-PERE PATERNEL'}, + {'id': 'MAIRIE', 'text': 'MAIRIE'}, + {'id': 'MERE', 'text': 'MERE'}, + {'id': 'O', 'text': 'ONCLE'}, + {'id': 'OS', 'text': 'ORGANISME SOCIAL'}, + {'id': 'PERE', 'text': 'PERE'}, + {'id': 'T', 'text': 'TANTE'}, + {'id': 'TUTEUR', 'text': 'TUTEUR'}, + ] + + +def test_read_sex_list(con, app): + url = get_endpoint('read-sex-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': 'M', 'text': 'Masculin'}, + {'id': 'F', 'text': 'Féminin'}, + ] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_situation_list(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = READ_SITUATIONS + url = get_endpoint('read-situation-list') + + resp = app.get(url) + assert resp.json['err'] == 0 + assert resp.json['data'] == [ + {'id': 'C', 'text': 'Célibataire'}, + {'id': 'D', 'text': 'Divorcé (e)'}, + {'id': 'CS', 'text': 'EN COURS DE SEPARATION'}, + {'id': 'M', 'text': 'Marié (e)'}, + {'id': 'P', 'text': 'Pacsé (e)'}, + {'id': 'S', 'text': 'Séparé (e)'}, + {'id': 'UL', 'text': 'UNION LIBRE'}, + {'id': 'V', 'text': 'Veuf (ve)'}, + {'id': 'VM', 'text': 'Vivant maritalement'}, + ] + + @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_family(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL - mocked_post.side_effect = [READ_FAMILY] + mocked_post.side_effect = [READ_FAMILY, READ_CATEGORIES, READ_SITUATIONS, READ_CIVILITIES, READ_QUALITIES] url = get_endpoint('read-family') Link.objects.create(resource=con, family_id='1312', name_id='local') @@ -175,7 +367,9 @@ def test_read_family(mocked_post, mocked_get, con, app): 'lastname': 'COSTANZE', 'firstname': 'DAMIEN', 'quality': 'PERE', + 'quality_text': 'PERE', 'civility': 'M.', + 'civility_text': 'Monsieur', 'dateBirth': '1980-10-07T00:00:00+01:00', 'adresse': { 'idStreet': 'AV0044', @@ -197,6 +391,27 @@ def test_read_family(mocked_post, mocked_get, con, app): 'profession': None, 'CAFInfo': {'number': '51', 'organ': None}, } + assert resp.json['data']['childList'][0] == { + 'num': '613880', + 'lastname': 'COSTANZE', + 'firstname': 'CASSANDRA', + 'sexe': 'F', + 'sexe_text': 'Féminin', + 'birth': {'dateBirth': '2021-06-22T00:00:00+02:00', 'place': None}, + 'dietcode': 'STD', + 'fsl': None, + 'bPhoto': False, + 'bLeaveAlone': False, + 'authorizedPersonList': [], + 'indicatorList': [], + 'medicalRecord': None, + 'subscribeSchoolList': [], + 'mother': None, + 'father': None, + 'rl': None, + 'subscribeActivityList': [], + 'paiInfoBean': None, + } def test_read_family_not_linked_error(con, app):