passerelle/passerelle/contrib/toulouse_maelis/models.py

3647 lines
149 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright (C) 2022 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import copy
import datetime
from operator import itemgetter
from urllib.parse import urljoin
import zeep
from dateutil import rrule
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.db.models import JSONField
from django.utils import dateformat
from django.utils.dateparse import parse_date
from django.utils.text import slugify
from django.utils.timezone import now
from requests import RequestException
from zeep.helpers import serialize_object
from zeep.wsse.username import UsernameToken
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
from passerelle.utils.conversion import simplify
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.templates import render_to_string
from . import activity_schemas, family_schemas, invoice_schemas, schemas, utils
class UpdateError(Exception):
pass
class ToulouseMaelis(BaseResource, HTTPResource):
# noqa pylint: disable=too-many-public-methods
base_wsdl_url = models.CharField(
max_length=128,
blank=False,
verbose_name='URL de base des WSDL',
default='https://demo-toulouse.sigec.fr/maelisws-toulouse/services/',
)
zeep_wsse_username = models.CharField(
max_length=64, blank=True, default='', verbose_name='Identifiant utilisateur WSSE'
)
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']
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'
wsdl_url = urljoin(self.base_wsdl_url, wsdl_name)
settings = zeep.Settings(strict=False, xsd_ignore_sequence_order=True)
return self.soap_client(wsdl_url=wsdl_url, wsse=wsse, settings=settings, api_error=True)
def call(self, wsdl_short_name, service, **kwargs):
client = self.get_client(wsdl_short_name)
method = getattr(client.service, service)
response = method(**kwargs)
return serialize_object(response)
def check_status(self):
assert self.call('Family', 'isWSRunning')
assert self.call('Activity', 'isWSRunning')
assert self.call('Invoice', 'isWSRunning')
assert self.call('Site', 'isWSRunning')
assert self.call('Ape', 'isWSRunning')
def update_referential(self, referential_name, data, id_key, text_key, delete=True):
last_update = now()
for item in data or []:
text = item[text_key] or ''
if isinstance(text, int):
text = str(text)
text = text.strip()
self.referential.update_or_create(
resource_id=self.id,
referential_name=referential_name,
item_id=item[id_key],
defaults={
'item_text': text,
'item_unaccent_text': simplify(text),
'item_data': dict({'id': item[id_key], 'text': text}, **item),
'updated': last_update,
},
)
if delete:
# delete extraneous items
self.referential.filter(referential_name=referential_name, updated__lt=last_update).delete()
def get_referential_data(self, service_name, referential_name):
try:
return self.call(service_name, 'read' + referential_name + 'List')
except Exception as e:
raise UpdateError('Service indisponible : %s' % str(e))
def update_family_referentials(self):
# local referentials
complement_data = [
{'id': 'B', 'text': 'bis'},
{'id': 'Q', 'text': 'quater'},
{'id': 'T', 'text': 'ter'},
]
sex_data = [
{'id': 'F', 'text': 'Féminin'},
{'id': 'M', 'text': 'Masculin'},
]
self.update_referential('Complement', complement_data, 'id', 'text')
self.update_referential('Sex', sex_data, 'id', 'text')
# remote referentials
for referential_name in (
'Category',
'ChildIndicator',
'Civility',
'Country',
'CSP',
'DietCode',
'Document',
'Organ',
'PAI',
'ProfessionalSituation',
'Quality',
'Quotient',
'RLIndicator',
'Situation',
'Street',
'Vaccin',
):
id_key, text_key = 'code', 'libelle'
data = self.get_referential_data('Family', referential_name)
if referential_name == 'Organ':
id_key, text_key = 'id', 'code'
elif referential_name == 'Street':
id_key, text_key = 'idStreet', 'libelleStreet'
self.update_referential(referential_name, data, id_key, text_key)
def update_site_referentials(self):
for referential_name in ('YearSchool', 'Level', 'DerogReason'):
id_key, text_key = 'code', 'libelle'
data = self.get_referential_data('Site', referential_name)
if referential_name == 'YearSchool':
id_key, text_key = 'schoolYear', 'schoolYear'
self.update_referential(referential_name, data, id_key, text_key)
def get_activity_catalog_raw(self, year):
return self.call(
'Activity',
'readActivityList',
schoolyear=year,
dateStartCalend='%s-09-01' % year,
dateEndCalend='%s-08-31' % (year + 1),
)
def update_activity_referentials(self):
for referential_name in ('ActivityNatureType', 'Direct', 'Service'):
id_key, text_key = 'code', 'libelle'
data = self.get_referential_data('Activity', referential_name)
if referential_name in ['Direct', 'Service']:
id_key, text_key = 'id', 'lib1'
self.update_referential(referential_name, data, id_key, text_key)
# put activity catalog per year as referential
data = []
reference_year = utils.get_reference_year_from_date(datetime.date.today())
for year in range(reference_year, reference_year + 2):
data.append(
{
'id': str(year),
'text': '%s-%s' % (year, year + 1),
'data': self.get_activity_catalog_raw(year),
}
)
self.update_referential('ActivityCatalog', data, 'id', 'text')
def update_ape_referentials(self):
indicators = self.call('Ape', 'readApeIndicatorList')
self.update_referential('ApeIndicator', indicators, 'level', 'level')
nurseries = self.call('Ape', 'readNurseryList', request={})
self.update_referential('Nursery', nurseries, 'idActivity', 'libelle')
def update_invoice_referentials(self):
data = self.get_referential_data('Invoice', 'Regie')
self.update_referential('Regie', data, 'code', 'libelle')
def daily(self):
try:
self.update_family_referentials()
self.update_site_referentials()
self.update_activity_referentials()
self.update_ape_referentials()
self.update_invoice_referentials()
except UpdateError as e:
self.logger.warning('Erreur sur la mise à jour: %s' % e)
else:
self.logger.info('Réferentiels mis à jour.')
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
if id is not None:
queryset = self.referential.filter(referential_name=referential_name, item_id=id)
else:
queryset = self.referential.filter(referential_name=referential_name).all()
if q:
queryset = queryset.filter(item_unaccent_text__icontains=simplify(q))
if distinct:
queryset = queryset.distinct('resource', 'referential_name', 'item_text')
if limit:
try:
limit = int(limit)
except ValueError:
pass
else:
queryset = queryset[:limit]
return [x.item_data for x in queryset]
def get_referential_value(self, referential_name, key):
try:
return self.referential.get(referential_name=referential_name, item_id=key).item_text
except Referential.DoesNotExist:
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)
except Link.DoesNotExist:
raise APIError('User not linked to family')
def get_family_raw(self, family_id, **kwargs):
return self.call('Family', 'readFamily', dossierNumber=family_id, **kwargs)
def get_rl_raw(self, family_id, rl_id, **kwargs):
data = self.get_family_raw(family_id, **kwargs)
if data['RL1']['num'] == rl_id:
return data['RL1']
elif data['RL2'] and data['RL2']['num'] == rl_id:
return data['RL2']
raise APIError("no '%s' RL on '%s' family" % (rl_id, family_id))
def get_person_raw(self, family_id, person_id):
data = self.get_family_raw(family_id)
for person in data['emergencyPersonList']:
if str(person['numPerson']) == person_id:
return person
raise APIError("no '%s' emergency person on '%s' family" % (person_id, family_id))
def get_child_raw(self, family_id, child_id):
data = self.get_family_raw(family_id)
for child in data['childList']:
if child['num'] == child_id:
return child
raise APIError("no '%s' child on '%s' family" % (child_id, family_id))
def get_rl_or_child_raw(self, family_id, person_id):
data = self.get_family_raw(family_id)
if data['RL1']['num'] == person_id:
return data['RL1']
elif data['RL2'] and data['RL2']['num'] == person_id:
return data['RL2']
for child in data['childList']:
if child['num'] == person_id:
return child
raise APIError("no '%s' RL or child on '%s' family" % (person_id, family_id))
def get_child_person_raw(self, family_id, child_id, person_id):
data = self.get_child_raw(family_id, child_id)
for person in data['authorizedPersonList']:
if str(person['personInfo']['num']) == person_id:
return person
raise APIError("no '%s' authorized person on '%s' child" % (person_id, child_id))
def add_text_value(self, referential_name, data, keys):
'''add text from referentials'''
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])
def add_indicators_field(self, referential_name, data):
active_indicators = {x['code']: x for x in data['indicatorList']}
indicators = self.get_referential(referential_name)
result = {}
for item in indicators:
active_indicator = active_indicators.get(item['id']) or {}
item['isActive'] = bool(active_indicator)
if item['typeDesc'] == 'NOTE':
item['note'] = active_indicator.get('note')
del item['choiceList'] # no list based indicator on parsifal project
result[item['id']] = item
data['indicators'] = result
def add_nature_subsciptions(self, data):
subscribe_natures = {}
for item in data['subscribeActivityList'] or []:
activity_type = item.get('typeActivity')
activity_nature = activity_type.get('natureSpec') if activity_type else None
if not activity_nature:
continue
for unit in item['subscribesUnit']:
start_year = utils.get_reference_year_from_date(unit.get('dateStart'))
end_year = utils.get_reference_year_from_date(unit.get('dateEnd'))
if not start_year or not end_year:
continue
for year in range(start_year, end_year + 1):
school_year = '%s-%s' % (year, year + 1)
if not subscribe_natures.get(school_year):
subscribe_natures[school_year] = set()
subscribe_natures[school_year].add(activity_nature['code'])
data['subscribe_natures'] = {x: sorted(list(y)) for x, y in subscribe_natures.items()}
def add_text_value_to_rl_indicator(self, data):
self.add_text_value('RLIndicator', data, ['code'])
def add_text_value_to_child_indicator(self, data):
self.add_text_value('ChildIndicator', data, ['code'])
def add_text_value_to_child_person(self, data):
self.add_text_value('Civility', data, ['personInfo', 'civility'])
self.add_text_value('Quality', data, ['personQuality', 'code'])
self.add_text_value('Sex', data, ['personInfo', 'sexe'])
def add_text_value_to_child(self, data):
self.add_text_value('Sex', data, ['sexe'])
self.add_text_value('DietCode', data, ['dietcode'])
self.add_text_value('PAI', data, ['paiInfoBean', 'code'])
for person in data['authorizedPersonList']:
self.add_text_value_to_child_person(person)
for indicator in data['indicatorList']:
self.add_text_value_to_child_indicator(indicator)
self.add_indicators_field('ChildIndicator', data)
self.add_nature_subsciptions(data)
def add_text_value_to_person(self, data):
self.add_text_value('Civility', data, ['civility'])
self.add_text_value('Quality', data, ['quality'])
self.add_text_value('Sex', data, ['sexe'])
def add_text_value_to_rl(self, data):
self.add_text_value('Civility', data, ['civility'])
self.add_text_value('Quality', data, ['quality'])
self.add_text_value('Complement', data, ['adresse', 'numComp'])
self.add_text_value('Street', data, ['adresse', 'idStreet'])
self.add_text_value('CSP', data, ['profession', 'codeCSP'])
self.add_text_value('ProfessionalSituation', data, ['profession', 'situation'])
self.add_text_value('Organ', data, ['CAFInfo', 'organ'])
for indicator in data['indicatorList']:
self.add_text_value_to_rl_indicator(indicator)
for quotient in data['quotientList']:
self.add_text_value('Quotient', quotient, ['cdquo'])
self.add_indicators_field('RLIndicator', data)
self.add_nature_subsciptions(data)
def add_text_value_to_family(self, data):
self.add_text_value('Category', data, ['category'])
self.add_text_value('Situation', data, ['situation'])
for rlg in 'RL1', 'RL2':
if data.get(rlg):
self.add_text_value_to_rl(data[rlg])
for child in data['childList']:
self.add_text_value_to_child(child)
for person in data['emergencyPersonList']:
self.add_text_value_to_person(person)
def get_child_person(self, family_id, child_id, person_id):
data = self.get_child_person_raw(family_id, child_id, person_id)
self.add_text_value_to_child_person(data)
return data
def get_child(self, family_id, child_id):
data = self.get_child_raw(family_id, child_id)
self.add_text_value_to_child(data)
return data
def get_person(self, family_id, person_id):
data = self.get_person_raw(family_id, person_id)
self.add_text_value_to_person(data)
return data
def get_rl(self, family_id, rl_id, **kwargs):
data = self.get_rl_raw(family_id, rl_id, **kwargs)
self.add_text_value_to_rl(data)
return data
def get_family(self, family_id, **kwargs):
data = self.get_family_raw(family_id, **kwargs)
self.add_text_value_to_family(data)
return data
def assert_key_in_referential(self, referential_name, key_value, keys_text, required=True):
if not key_value:
if required:
raise APIError("%s is required and could not be None" % keys_text)
return
try:
self.referential.get(referential_name=referential_name, item_id=key_value)
except Referential.DoesNotExist:
ref_text = "required " if required else ""
ref_text = ref_text + "referential"
raise APIError(
"%s key value '%s' do not belong to '%s' %s"
% (keys_text, key_value, referential_name, ref_text)
)
def assert_post_data_in_referential(self, referential_name, data, keys, required=True):
key_value = None
for key in keys:
if not (isinstance(data, list) and isinstance(key, int)) and not (
isinstance(data, dict) and key in data
):
break
data = data[key]
else:
key_value = data
self.assert_key_in_referential(referential_name, key_value, '/'.join(str(x) for x in keys), required)
def encode_bool(self, obj):
if obj is True or str(obj).lower() in ['true', 'oui', '1']:
return True
if obj is False or str(obj).lower() in ['false', 'non', '0']:
return False
def check_and_adapt_update_indicator_payload_in_referential(
self, referential, post_data, parent_keys=None
):
keys = parent_keys or []
data = post_data
for key in keys:
data = data[key]
for i, item in enumerate(data.get('indicatorList', [])):
self.assert_post_data_in_referential(
referential, post_data, keys + ['indicatorList', i, 'code'], required=True
)
item['isActive'] = self.encode_bool(item['isActive'])
def check_and_adapt_child_medical_record_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
data = post_data
for key in keys:
data = data[key]
if 'isAuthHospital' in data:
data['isAuthHospital'] = self.encode_bool(data['isAuthHospital'])
for i in range(0, len(data.get('vaccinList', []))):
self.assert_post_data_in_referential(
'Vaccin', post_data, keys + ['vaccinList', i, 'code'], required=False
)
def check_and_adapt_child_pai_payoad_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential('PAI', post_data, keys + ['code'])
def check_and_adapt_child_person_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential(
'Civility', post_data, keys + ['personInfo', 'civility'], required=False
)
self.assert_post_data_in_referential('Sex', post_data, keys + ['personInfo', 'sexe'], required=False)
self.assert_post_data_in_referential('Quality', post_data, keys + ['personQuality', 'code'])
def check_and_adapt_child_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential('Sex', post_data, keys + ['sexe'])
data = post_data
for key in keys:
data = data[key]
for key in ('bPhoto', 'bLeaveAlone'):
if key in data:
data[key] = self.encode_bool(data[key])
if 'dietcode' in data:
self.assert_post_data_in_referential('DietCode', post_data, keys + ['dietcode'], required=False)
if 'paiInfoBean' in data:
self.check_and_adapt_child_pai_payoad_in_referential(post_data, keys + ['paiInfoBean'])
if 'medicalRecord' in data:
# dead code as updateFamily seems not to modify medicalRecord
self.check_and_adapt_child_medical_record_payload_in_referential(
post_data, keys + ['medicalRecord']
)
self.check_and_adapt_update_indicator_payload_in_referential('ChildIndicator', post_data, keys)
def check_and_adapt_person_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential('Civility', post_data, keys + ['civility'], required=False)
self.assert_post_data_in_referential('Sex', post_data, keys + ['sexe'], required=False)
self.assert_post_data_in_referential('Quality', post_data, keys + ['quality'])
def check_and_adapt_update_coordinate_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential(
'Street', post_data, keys + ['adresse', 'idStreet'], required=False
)
self.assert_post_data_in_referential(
'Complement', post_data, keys + ['adresse', 'numComp'], required=False
)
self.assert_post_data_in_referential(
'CSP', post_data, keys + ['profession', 'codeCSP'], required=False
)
self.assert_post_data_in_referential(
'ProfessionalSituation', post_data, keys + ['profession', 'situation'], required=False
)
self.assert_post_data_in_referential('Organ', post_data, keys + ['CAFInfo', 'organ'], required=False)
data = post_data
for key in keys:
data = data[key]
if 'contact' in data:
data = data['contact']
for key in ('isContactMail', 'isContactSms', 'isInvoicePdf'):
if key in data:
data[key] = self.encode_bool(data[key])
def check_and_adapt_rl_payload_in_referential(self, post_data, parent_keys=None):
keys = parent_keys or []
self.assert_post_data_in_referential('Civility', post_data, keys + ['civility'])
self.assert_post_data_in_referential('Quality', post_data, keys + ['quality'])
self.check_and_adapt_update_coordinate_payload_in_referential(post_data, keys)
self.check_and_adapt_update_indicator_payload_in_referential('RLIndicator', post_data, keys)
def check_and_adapt_create_rl1_payload_in_referential(self, post_data):
self.assert_post_data_in_referential('Category', post_data, ['category'])
self.assert_post_data_in_referential('Situation', post_data, ['situation'])
self.check_and_adapt_rl_payload_in_referential(post_data, ['rl1'])
def check_and_adapt_family_payload_in_referential(self, post_data):
self.assert_post_data_in_referential('Category', post_data, ['category'])
self.assert_post_data_in_referential('Situation', post_data, ['situation'])
for rlg in 'rl1', 'rl2':
if rlg in post_data:
self.check_and_adapt_rl_payload_in_referential(post_data, [rlg])
for i, person in enumerate(post_data.get('emergencyPersonList') or []):
for j in range(0, len(person.get('personList') or [])):
self.check_and_adapt_person_payload_in_referential(
post_data, ['emergencyPersonList', i, 'personList', j]
)
for i in range(0, len(post_data.get('childList') or [])):
self.check_and_adapt_child_payload_in_referential(post_data, ['childList', i])
if 'flagCom' in post_data:
post_data['flagCom'] = self.encode_bool(post_data['flagCom'])
def replace_null_values(self, dico):
'''send null fields as empty SOAP tag to tell maelis to empty the value'''
for key, value in dico.items():
if isinstance(value, dict):
self.replace_null_values(value)
if value is None:
dico[key] = ''
def read_rl_list_raw(self, family_id, text_template=None, income_year=None):
result = self.get_family_raw(family_id, incomeYear=income_year)
if not text_template:
text_template = '{{ lastname }} {{ firstname }}'
for rlg in 'RL1', 'RL2':
item = result.get(rlg)
if not item:
break
self.add_text_value_to_rl(item)
item['id'] = item['num']
item['text'] = render_to_string(text_template, item).strip()
item['family_id'] = family_id
yield item
def read_child_list_raw(self, family_id, text_template=None):
result = self.get_family_raw(family_id)
if not text_template:
text_template = '{{ lastname }} {{ firstname }}'
for item in result['childList']:
self.add_text_value_to_child(item)
item['id'] = item['num']
item['text'] = render_to_string(text_template, item).strip()
item['family_id'] = family_id
yield item
def get_person_activity_list_raw(
self,
family_id,
person_id,
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,
'yearSchool': reference_year,
'dateStartActivity': start_date,
'dateEndActivity': end_date,
}
data = self.call(
'Activity', 'getPersonCatalogueActivity', getPersonCatalogueActivityRequestBean=params
)
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):
return (
self.call(
'Activity',
'getFamilyBasket',
getFamilyBasketRequestBean={
'numFamily': family_id,
},
)
or []
)
def get_basket_raw(self, family_id, basket_id):
for basket in self.get_baskets_raw(family_id):
if basket['id'] == basket_id:
return basket
raise APIError("no '%s' basket on family" % basket_id)
@endpoint(
display_category='Famille',
description='Lister les catégories',
name='read-category-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_category_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Category', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les indicateurs sur le enfants',
name='read-child-indicator-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_child_indicator_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('ChildIndicator', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les civilités',
name='read-civility-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_civility_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Civility', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les compléments du numéro de voie',
name='read-complement-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_complement_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Complement', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les pays',
name='read-country-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_country_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Country', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='lister les catégories socio-professionnelles',
name='read-csp-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_csp_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('CSP', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les régimes alimentaires',
name='read-dietcode-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_dietcode_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('DietCode', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les pièces jointes',
name='read-document-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_document_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Document', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les organismes (CAF)',
name='read-organ-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_organ_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Organ')}
@endpoint(
display_category='Famille',
description="Lister les Projets d'Accueils Individualisés (PAI)",
name='read-pai-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_pai_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('PAI', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description="Lister les situations professionnelles",
name='read-professional-situation-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Supression des doublons'},
},
)
def read_professional_situation_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('ProfessionalSituation', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='lister les qualités',
name='read-quality-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_quality_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Quality', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les quotients',
name='read-quotient-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_quotient_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Quotient', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les indicateurs sur les responsables légaux',
name='read-rl-indicator-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_rl_indicator_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('RLIndicator')}
@endpoint(
display_category='Famille',
description='Lister les sexes',
name='read-sex-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_sex_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Sex', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les situations',
name='read-situation-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_situation_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Situation', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='lister les voies',
name='read-street-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_street_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Street', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lister les vaccins',
name='read-vaccin-list',
perm='can_access',
parameters={
'id': {'description': 'Identifiant de lenregistrement'},
'q': {'description': 'Recherche en texte intégral'},
'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
'distinct': {'description': 'Ne pas inclure les valeurs dupliquées'},
},
)
def read_vaccin_list(self, request, id=None, q=None, limit=None, distinct=True):
return {'data': self.get_referential('Vaccin', id, q, limit, distinct)}
@endpoint(
display_category='Famille',
description='Lier un compte usager à une famille',
perm='can_access',
parameters={'NameID': {'description': 'Publik NameID'}},
post={'request_body': {'schema': {'application/json': schemas.LINK_SCHEMA}}},
)
def link(self, request, NameID, post_data):
family_id = post_data['family_id']
response = self.call('Family', 'readFamily', dossierNumber=family_id)
if not (
response['RL1']['firstname'] == post_data['firstname'].upper()
and response['RL1']['lastname'] == post_data['lastname'].upper()
and response['RL1']['birth']['dateBirth'].strftime('%Y-%m-%d') == post_data['dateBirth']
):
raise APIError("RL1 does not match '%s' family" % family_id)
Link.objects.update_or_create(resource=self, name_id=NameID, defaults={'family_id': family_id})
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description='Supprimer une liaison entre un compte usager et une famille',
methods=['post'],
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
},
)
def unlink(self, request, NameID):
link = self.get_link(NameID)
link.delete()
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Rechercher un dossier famille",
name='search-family',
perm='can_access',
parameters={
'q': {'description': 'Recherche en texte intégral'},
},
)
def search_family(self, request, q=None):
data = []
if q and len(q) >= 4: # speedup maelis reply
response = self.call('Family', 'readFamilyListFromFullName', fullname=q)
data = serialize_object(response)
return {'data': data}
@endpoint(
display_category='Famille',
description="Rechercher un dossier famille par son numéro de DUI",
name='search-family-dui',
perm='can_access',
parameters={
'q': {'description': 'Numéro de DUI'},
},
)
def search_family_dui(self, request, q=None):
data = []
if q:
try:
response = self.call('Family', 'readFamilyList', dossierNumber=q)
except APIError:
pass
else:
data = serialize_object(response)
return {'data': data}
@endpoint(
display_category='Famille',
description='Obtenir les informations sur la famille',
perm='can_access',
name='read-family',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'income_year': {'description': 'Année de revenu pour filtrer les quotients'},
},
)
def read_family(self, request, NameID=None, family_id=None, income_year=None):
family_id = family_id or self.get_link(NameID).family_id
data = self.get_family(family_id, incomeYear=income_year)
data['family_id'] = family_id
return {'data': data}
@endpoint(
display_category='Famille',
description="Lister les responsables légaux",
perm='can_access',
name='read-rl-list',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ lastname }} {{ firstname }}',
},
'income_year': {'description': 'Année de revenu pour filtrer les quotients'},
},
)
def read_rl_list(self, request, NameID=None, family_id=None, text_template=None, income_year=None):
family_id = family_id or self.get_link(NameID).family_id
return {'data': list(self.read_rl_list_raw(family_id, text_template))}
@endpoint(
display_category='Famille',
description="Lister les personnes à prévenir en cas d'urgence",
perm='can_access',
name='read-person-list',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ lastname }} {{ firstname }}',
},
},
)
def read_person_list(self, request, NameID=None, family_id=None, text_template=None):
family_id = family_id or self.get_link(NameID).family_id
result = self.get_family_raw(family_id)
if not text_template:
text_template = '{{ lastname }} {{ firstname }}'
data = []
for item in result['emergencyPersonList']:
self.add_text_value_to_person(item)
item['id'] = item['numPerson']
item['text'] = render_to_string(text_template, item).strip()
item['family_id'] = family_id
data.append(item)
return {'data': data}
@endpoint(
display_category='Famille',
description="Lister les enfants",
perm='can_access',
name='read-child-list',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ lastname }} {{ firstname }}',
},
},
)
def read_child_list(self, request, NameID=None, family_id=None, text_template=None):
family_id = family_id or self.get_link(NameID).family_id
return {'data': list(self.read_child_list_raw(family_id, text_template))}
@endpoint(
display_category='Famille',
description="Lister les enfants et les responsables légaux",
perm='can_access',
name='read-rl-and-child-list',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'rl_text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ lastname }} {{ firstname }}',
},
'child_text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ lastname }} {{ firstname }}',
},
},
)
def read_rl_and_child_list(
self, request, NameID=None, family_id=None, rl_text_template=None, child_text_template=None
):
family_id = family_id or self.get_link(NameID).family_id
return {
'data': list(self.read_rl_list_raw(family_id, rl_text_template))
+ list(self.read_child_list_raw(family_id, child_text_template))
}
@endpoint(
display_category='Famille',
description="Lister les personnes autorisées à récupérer l'enfant",
perm='can_access',
name='read-child-person-list',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text',
'example_value': '{{ personInfo.lastname }} {{ personInfo.firstname }}',
},
},
)
def read_child_person_list(self, request, child_id, NameID=None, family_id=None, text_template=None):
family_id = family_id or self.get_link(NameID).family_id
result = self.get_child_raw(family_id, child_id)
if not text_template:
text_template = '{{ personInfo.lastname }} {{ personInfo.firstname }}'
data = []
for item in result['authorizedPersonList']:
self.add_text_value_to_child_person(item)
item['id'] = item['personInfo']['num']
item['text'] = render_to_string(text_template, item).strip()
item['family_id'] = family_id
data.append(item)
return {'data': data}
@endpoint(
display_category='Famille',
description="Obtenir les informations sur un responsable légal",
perm='can_access',
name='read-rl',
parameters={
'rl_id': {'description': 'Numéro du représentant légal'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'income_year': {'description': 'Année de revenu pour filtrer les quotients'},
},
)
def read_rl(
self,
request,
rl_id,
NameID=None,
family_id=None,
income_year=None,
):
family_id = family_id or self.get_link(NameID).family_id
data = self.get_rl(family_id, rl_id, incomeYear=income_year)
data['family_id'] = family_id
return {'data': data}
@endpoint(
display_category='Famille',
description="Obtenir les informations sur une personne à prévenir en cas d'urgence",
perm='can_access',
name='read-person',
parameters={
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_person(self, request, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
data = self.get_person(family_id, person_id)
data['family_id'] = family_id
return {'data': data}
@endpoint(
display_category='Famille',
description="Obtenir les informations sur un enfant",
perm='can_access',
name='read-child',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_child(self, request, child_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
data = self.get_child(family_id, child_id)
data['family_id'] = family_id
return {'data': data}
@endpoint(
display_category='Famille',
description="Obtenir les informations sur une personne autorisée à venir chercher l'enfant",
perm='can_access',
name='read-child-person',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_child_person(self, request, child_id, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
data = self.get_child_person(family_id, child_id, person_id)
data['family_id'] = family_id
return {'data': data}
@endpoint(
display_category='Famille',
description="Vérifier qu'un responsable légal existe",
perm='can_access',
name='is-rl-exists',
post={'request_body': {'schema': {'application/json': family_schemas.ISEXISTS_SCHEMA}}},
)
def is_rl_exists(self, request, post_data):
response = self.call('Family', 'isRLExists', **post_data)
return {'data': response}
@endpoint(
display_category='Famille',
description="Vérifier qu'un enfant existe",
perm='can_access',
name='is-child-exists',
post={'request_body': {'schema': {'application/json': family_schemas.ISEXISTS_SCHEMA}}},
)
def is_child_exists(self, request, post_data):
response = self.call('Family', 'isChildExists', **post_data)
return {'data': response}
@endpoint(
display_category='Famille',
description="Lister les activités auxquelles un RL ou un enfant est inscrit",
perm='can_access',
name='read-subscribe-activity-list',
parameters={
'child_id': {'description': "Numéro du représentant légal ou de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'nature': {
'description': "Natures des activités : PERICSO, EXTRASCO ou LOISIR (toutes par défaut)",
},
'type_ids': {
'description': "Codes des types des activités (tous par défaut), séparés par des virgules",
'example_value': 'ACCSOIR,RESTSCOL',
},
},
)
def read_subscribe_activity_list(
self, request, person_id, NameID=None, family_id=None, nature=None, type_ids=None
):
family_id = family_id or self.get_link(NameID).family_id
result = self.get_rl_or_child_raw(family_id, person_id)
if str(nature).lower() == 'perisco':
nature_filter_codes = self.get_perisco_nature_codes()
elif 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()]
data = []
for item in result['subscribeActivityList'] or []:
activity_type = item.get('typeActivity')
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
item['id'] = item['idActivity']
item['text'] = item['libelle']
data.append(item)
return {'data': data}
@endpoint(
display_category='Famille',
description='Créer la famille',
name='create-family',
perm='can_access',
parameters={'NameID': {'description': 'Publik NameID'}},
post={'request_body': {'schema': {'application/json': family_schemas.CREATE_FAMILY_SCHEMA}}},
)
def create_family(self, request, post_data, NameID=None):
if self.link_set.filter(name_id=NameID).exists():
raise APIError('User already linked to family')
self.check_and_adapt_family_payload_in_referential(post_data)
response = self.call('Family', 'createFamily', **post_data)
family_id = response.get('number')
if not family_id:
errors = response.get('rl1ErrorList') + response.get('childErrorList')
raise APIError(' ; '.join(errors))
if NameID:
Link.objects.create(resource=self, name_id=NameID, family_id=family_id)
return {'data': response}
@endpoint(
display_category='Famille',
description='Modifier la famille',
name='update-family',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_FAMILY_SCHEMA}}},
)
def update_family(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_family_payload_in_referential(post_data)
self.replace_null_values(post_data)
# adapt payload to use same input as create_family
if len(post_data.get('emergencyPersonList', [])):
persons = post_data.pop('emergencyPersonList')
post_data['emergencyPersonList'] = [{'personList': persons}]
response = self.call('Family', 'updateFamily', dossierNumber=family_id, **post_data)
family_id = response.get('number')
errors = response.get('childErrorList')
if errors:
raise APIError(' ; '.join(errors))
return {'data': response}
@endpoint(
display_category='Famille',
description='Créer le RL1',
name='create-rl1',
perm='can_access',
parameters={'NameID': {'description': 'Publik NameID'}},
post={'request_body': {'schema': {'application/json': family_schemas.CREATE_RL1_SCHEMA}}},
)
def create_rl1(self, request, post_data, NameID=None):
if self.link_set.filter(name_id=NameID).exists():
raise APIError('User already linked to family')
self.check_and_adapt_create_rl1_payload_in_referential(post_data)
response = self.call('Family', 'createFamily', **post_data)
family_id = response.get('number')
if not family_id:
errors = response.get('rl1ErrorList') or []
raise APIError(' ; '.join(errors))
if NameID:
Link.objects.create(resource=self, name_id=NameID, family_id=family_id)
return {'data': {'family_id': family_id}}
@endpoint(
display_category='Famille',
description='Modifier le RL1',
name='update-rl1',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_RL1_SCHEMA}}},
)
def update_rl1(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_rl_payload_in_referential(post_data)
self.replace_null_values(post_data)
family = self.get_family_raw(family_id)
rl1 = post_data
rl1['adresse'] = family['RL1']['adresse']
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'rl1': rl1,
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description='Créer le RL2',
name='create-rl2',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.CREATE_RL2_SCHEMA}}},
)
def create_rl2(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_rl_payload_in_referential(post_data)
family = self.get_family_raw(family_id)
if family['RL2']:
raise APIError('RL2 already defined on family')
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'rl2': post_data,
}
response = self.call('Family', 'updateFamily', **payload)
return {'data': {'id': response['RL2']['num']}}
@endpoint(
display_category='Famille',
description='Modifier le RL2',
name='update-rl2',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_RL2_SCHEMA}}},
)
def update_rl2(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_rl_payload_in_referential(post_data)
self.replace_null_values(post_data)
family = self.get_family_raw(family_id)
if not family['RL2']:
raise APIError('No RL2 to update on family')
rl2 = post_data
rl2['adresse'] = family['RL2']['adresse']
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'rl2': rl2,
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Ajouter un enfant",
name='create-child',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'force': {
'description': 'boolean to bypass doublon error',
'type': 'bool',
'example_value': 'false',
},
},
post={'request_body': {'schema': {'application/json': family_schemas.CREATE_CHILD_SCHEMA}}},
)
def create_child(self, request, post_data, NameID=None, family_id=None, force=False):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_child_payload_in_referential(post_data)
payload = {
'numDossier': family_id,
'isForceCreateChild': force,
'child': post_data,
}
response = self.call('Family', 'createChild', **payload)
child_id = response.get('number')
if not child_id:
errors = response.get('childErrorList') or []
raise APIError(' ; '.join(errors))
return {'data': {'child_id': child_id}}
@endpoint(
display_category='Famille',
description="Modifier un enfant",
name='update-child',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_CHILD_SCHEMA}}},
)
def update_child(self, request, post_data, child_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_child_payload_in_referential(post_data)
self.replace_null_values(post_data)
family = self.get_family_raw(family_id)
child = post_data
child['num'] = child_id
for known_child in family['childList']:
if str(known_child['num']) == child_id:
child['authorizedPersonList'] = known_child['authorizedPersonList']
break
else:
raise APIError('No child %s to update on family' % child_id)
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'childList': [child],
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Mettre à jour les coordonnées d'un responsable légal",
name='update-coordinate',
perm='can_access',
parameters={
'rl_id': {'description': 'Numéro du représentant légal'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_COORDINATE_SCHEMA}}},
)
def update_coordinate(self, request, post_data, rl_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_update_coordinate_payload_in_referential(post_data)
self.replace_null_values(post_data)
self.call('Family', 'updateCoordinate', numDossier=family_id, numPerson=rl_id, **post_data)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Mettre à jour les indicateurs d'un responsable légal",
name='update-rl-indicator',
perm='can_access',
parameters={
'rl_id': {'description': 'Numéro du représentant légal'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_INDICATOR_SCHEMA}}},
)
def update_rl_indicator(self, request, post_data, rl_id, NameID=None, family_id=None):
assert family_id or self.get_link(NameID)
self.check_and_adapt_update_indicator_payload_in_referential('RLIndicator', post_data)
self.call('Family', 'updatePersonIndicatorList', numPerson=rl_id, **post_data)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer ou mettre à jour le quotient d'un responsable légal",
name='update-quotient',
perm='can_access',
parameters={
'rl_id': {'description': "Numéro du responsable légal"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_QUOTIENT_SCHEMA}}},
)
def update_quotient(self, request, post_data, rl_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.assert_post_data_in_referential('Quotient', post_data, ['cdquo'])
payload = {
'dossierNumber': family_id,
'personNumber': rl_id,
'quotient': post_data,
}
self.call('Family', 'createUpdateQuotient', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer une personne à prévenir en cas d'urgence",
name='create-person',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.EMERGENCY_PERSON_SCHEMA}}},
)
def create_person(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_person_payload_in_referential(post_data)
family = self.get_family_raw(family_id)
personList = family['emergencyPersonList']
personList.append(post_data)
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'emergencyPersonList': [{'personList': personList}],
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Mettre à jour une personne à prévenir en cas d'urgence",
name='update-person',
perm='can_access',
parameters={
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.EMERGENCY_PERSON_SCHEMA}}},
)
def update_person(self, request, post_data, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_person_payload_in_referential(post_data)
family = self.get_family_raw(family_id)
personList = family['emergencyPersonList']
for i, person in enumerate(personList):
if str(person['numPerson']) == person_id:
personList[i] = post_data
personList[i]['numPerson'] = person_id
break
else:
raise APIError("no '%s' authorized person on '%s' family" % (person_id, family_id))
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'emergencyPersonList': [{'personList': personList}],
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Supprimer une personne à prévenir en cas d'urgence",
name='delete-person',
perm='can_access',
parameters={
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
methods=['post'],
)
def delete_person(self, request, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
family = self.get_family_raw(family_id)
personList = family['emergencyPersonList']
for i, person in enumerate(personList):
if str(person['numPerson']) == person_id:
del personList[i]
break
else:
raise APIError("no '%s' authorized person on '%s' family" % (person_id, family_id))
payload = {
'dossierNumber': family_id,
'category': family['category'],
'situation': family['situation'],
'flagCom': family['flagCom'],
'nbChild': family['nbChild'],
'nbTotalChild': family['nbTotalChild'],
'nbAES': family['nbAES'],
'emergencyPersonList': [{'personList': personList}],
}
self.call('Family', 'updateFamily', **payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer une personne autorisée à venir chercher l'enfant",
name='create-child-person',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.AUTHORIZED_PERSON_SCHEMA}}},
)
def create_child_person(self, request, post_data, child_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_child_person_payload_in_referential(post_data)
child = self.get_child_raw(family_id, child_id)
personList = child['authorizedPersonList']
personList.append(post_data)
req = {
'numFamily': family_id,
'numPerson': child_id,
'bLeaveAlone': child['bLeaveAlone'],
'bPhoto': child['bPhoto'],
'personList': personList,
}
self.call('Family', 'updateChildAutorization', updateChildAutorizationRequest=req)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Mettre à jour une personne autorisée à venir chercher l'enfant",
name='update-child-person',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.AUTHORIZED_PERSON_SCHEMA}}},
)
def update_child_person(self, request, post_data, child_id, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.check_and_adapt_child_person_payload_in_referential(post_data)
child = self.get_child_raw(family_id, child_id)
personList = child['authorizedPersonList']
for i, person in enumerate(personList):
if str(person['personInfo']['num']) == person_id:
personList[i] = post_data
personList[i]['personInfo']['num'] = person_id
break
else:
raise APIError("No '%s' authorized person on '%s' child" % (person_id, child_id))
req = {
'numFamily': family_id,
'numPerson': child_id,
'bLeaveAlone': child['bLeaveAlone'],
'bPhoto': child['bPhoto'],
'personList': personList,
}
self.call('Family', 'updateChildAutorization', updateChildAutorizationRequest=req)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Supprimer une personne autorisée à venir chercher l'enfant",
name='delete-child-person',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'person_id': {'description': 'Numéro de la personne'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
methods=['post'],
)
def delete_child_person(self, request, child_id, person_id, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
child = self.get_child_raw(family_id, child_id)
personList = child['authorizedPersonList']
for i, person in enumerate(personList):
if str(person['personInfo']['num']) == person_id:
del personList[i]
break
else:
raise APIError("No '%s' authorized person on '%s' child" % (person_id, child_id))
req = {
'numFamily': family_id,
'numPerson': child_id,
'bLeaveAlone': child['bLeaveAlone'],
'bPhoto': child['bPhoto'],
'personList': personList,
}
self.call('Family', 'updateChildAutorization', updateChildAutorizationRequest=req)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer ou mettre à jour le régime alimentaire d'un enfant",
name='update-child-dietcode',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'dietcode': {'description': 'code du régime alimentaire'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
methods=['post'],
)
def update_child_dietcode(self, request, child_id, dietcode, NameID=None, family_id=None):
assert family_id or self.get_link(NameID)
self.assert_key_in_referential('DietCode', dietcode, 'dietcode parameter', required=False)
self.call('Family', 'createOrUpdateChildDiet', personNumber=child_id, code=dietcode)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer ou mettre à jour les informations relatives au PAI d'un enfant",
name='update-child-pai',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.PAIINFO_SCHEMA}}},
)
def update_child_pai(self, request, post_data, child_id, NameID=None, family_id=None):
assert family_id or self.get_link(NameID)
self.check_and_adapt_child_pai_payoad_in_referential(post_data)
# use None to empty date passed as an empty string by date filter
for key in ('dateDeb', 'dateFin'):
if post_data[key] == '':
post_data[key] = None
self.call('Family', 'updateChildPAI', personNumber=child_id, **post_data)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Créer ou mettre à jour les données médicales d'un enfant",
name='update-child-medical-record',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.MEDICALRECORD_SCHEMA}}},
)
def update_child_medical_record(self, request, post_data, child_id, NameID=None, family_id=None):
assert family_id or self.get_link(NameID)
self.check_and_adapt_child_medical_record_payload_in_referential(post_data)
self.replace_null_values(post_data)
payload = {
'numPerson': child_id,
'medicalRecord': post_data,
}
self.call('Family', 'updateChildMedicalRecord', updateChildMedicalRecordRequest=payload)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description="Mettre à jour des indicateurs d'un enfant",
name='update-child-indicator',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.UPDATE_INDICATOR_SCHEMA}}},
)
def update_child_indicator(self, request, post_data, child_id, NameID=None, family_id=None):
assert family_id or self.get_link(NameID)
self.check_and_adapt_update_indicator_payload_in_referential('ChildIndicator', post_data)
self.call('Family', 'updatePersonIndicatorList', numPerson=child_id, **post_data)
return {'data': 'ok'}
@endpoint(
display_category='Famille',
description='Ajouter un document pour une famille, un responsable légal ou un enfant',
name='add-supplied-document',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': family_schemas.SUPPLIED_DOCUMENTS_SCHEMA}}},
)
def add_supplied_document(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
for i in range(0, len(post_data.get('documentList', []))):
self.assert_post_data_in_referential('Document', post_data, ['documentList', i, 'code'])
for item in post_data['documentList']:
file = item.pop('file')
item['filename'] = file['filename']
item['fileSupplied'] = {
'dataHandler': base64.b64decode(file['content']),
'fileType': file['content_type'],
'name': file['filename'],
}
payload = {'addSuppliedDocumentRequestBean': post_data}
payload['addSuppliedDocumentRequestBean']['numDossier'] = family_id
response = self.call('Family', 'addSuppliedDocument', **payload)
if response != 'OK':
raise APIError('maelis fails to add the supplied document')
return {'data': 'ok'}
def get_start_and_end_dates(self, start_date, end_date):
try:
start_date = datetime.datetime.strptime(start_date, utils.json_date_format).date()
end_date = datetime.datetime.strptime(end_date, utils.json_date_format).date()
except ValueError:
raise APIError('bad date format, should be YYYY-MM-DD', http_status=400)
if start_date > end_date:
raise APIError('start_date should be before end_date', http_status=400)
reference_year = utils.get_reference_year_from_date(start_date)
end_reference_year = utils.get_reference_year_from_date(end_date)
if reference_year != end_reference_year:
raise APIError(
'start_date and end_date are in different reference year (%s != %s)'
% (reference_year, end_reference_year),
http_status=400,
)
return start_date, end_date, reference_year
def get_bookings(self, family_id, child_id, start_date, end_date):
bookings = []
for booking_date in rrule.rrule(rrule.MONTHLY, dtstart=start_date.replace(day=1), until=end_date):
payload = {
'requestBean': {
'numDossier': family_id,
'numPerson': child_id,
'year': booking_date.year,
'month': booking_date.month,
}
}
response = self.call('Activity', 'getPersonScheduleList', **payload)
for result_data in response or []:
for schedule in result_data['activityScheduleList']:
activity = schedule['activity']
if not activity['activityType']['natureSpec']:
continue
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
for unit in schedule['unitScheduleList']:
days = unit['dayInfoList']
for day in days:
if day['status'] in ['NO_READ', 'NO_CUSTODY']:
continue
booking = {
'id': '%s:%s:%s'
% (child_id, activity_id, day['day'].strftime(utils.json_date_format)),
'text': dateformat.format(day['day'], 'l j F Y'),
'prefill': day['scheduledPresence'] > 0 or day['realPresence'] > 1,
'disabled': (
day['status'] != 'WRITABLE'
or activity['activityType']['natureSpec']['code']
in self.get_extrasco_nature_codes()
),
'details': day,
}
color = 'white'
if booking['prefill']:
color = 'green'
booking['details']['status_color'] = color
booking['details']['activity_id'] = activity_id
booking['details']['activity_type'] = activity['activityType']['code']
if (
activity['activityType']['natureSpec']['code']
in self.get_perisco_nature_codes()
):
booking['details']['activity_label'] = activity['activityType']['libelle']
else:
booking['details']['activity_label'] = (
activity['libelle2'] or activity['libelle']
)
if many_units:
booking['details']['activity_label'] += ' (%s)' % unit['unit']['libelle']
booking['details']['child_id'] = child_id
booking['details']['day_str'] = day['day'].strftime(utils.json_date_format)
booking['details']['unit_id'] = unit['unit']['idUnit']
bookings.append(booking)
# sort bookings
activity_types = ['ACCMAT', 'RESTSCOL', 'ACCPERI', 'ACCSOIR']
bookings = [
(
b['details']['day'],
activity_types.index(b['details']['activity_type'])
if b['details']['activity_type'] in activity_types
else 0,
b['details']['activity_label'],
b,
)
for b in bookings
]
bookings = sorted(bookings, key=itemgetter(0, 1, 2))
bookings = [b for d, a, l, b in bookings]
return bookings
@endpoint(
display_category='Réservation',
description="Obtenir l'agenda d'un enfant",
name='read-child-agenda',
perm='can_access',
parameters={
'child_id': {'description': "Numéro de l'enfant"},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_child_agenda(self, request, child_id, start_date, end_date, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
bookings = self.get_bookings(family_id, child_id, start_date, end_date)
return {
'data': bookings,
'extra_data': {
'start_date': start_date,
'end_date': end_date,
'school_year': '%s/%s' % (reference_year, reference_year + 1),
},
}
@endpoint(
display_category='Réservation',
description="Modifier l'agenda d'un enfant",
name='update-child-agenda',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={
'request_body': {
'schema': {
'application/json': activity_schemas.BOOKING_SCHEMA,
}
}
},
)
def update_child_agenda(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
child_id = post_data['child_id']
start_date, end_date, dummy = self.get_start_and_end_dates(
post_data['start_date'], post_data['end_date']
)
requested_bookings = post_data['booking_list']
# build list of existing booked days
bookings = self.get_bookings(family_id, child_id, start_date, end_date)
legacy_bookings = [b['id'] for b in bookings if b['prefill'] is True]
available_bookings = [b['id'] for b in bookings if b['disabled'] is False]
bookings_to_update = []
updated = []
for booking_info in bookings:
day_id = booking_info['id']
booked = None
action = booking_info['details']['action']
if day_id not in available_bookings:
# disabled or not available: not bookable
booked = None
elif (
day_id not in legacy_bookings
and day_id in requested_bookings
and action in ['ADD_PRES_PREVI', 'ADD_PRES_REAL', 'DEL_ABSENCE']
):
booked = action
elif (
day_id in legacy_bookings
and day_id not in requested_bookings
and action in ['DEL_PRES_PREVI', 'DEL_PRES_REAL', 'ADD_ABSENCE']
):
booked = action
if booked is not None:
# no changes, don't send the day
bookings_to_update.append(
{
'numPerson': child_id,
'idAct': booking_info['details']['activity_id'],
'idUni': booking_info['details']['unit_id'],
'date': booking_info['details']['day_str'],
'action': booked,
}
)
updated.append(
{
'activity_id': booking_info['details']['activity_id'],
'activity_type': booking_info['details']['activity_type'],
'activity_label': booking_info['details']['activity_label'],
'day': booking_info['details']['day_str'],
'booked': booked in ['ADD_PRES_PREVI', 'ADD_PRES_REAL', 'DEL_ABSENCE'],
}
)
if not bookings_to_update:
# don't call maelis if no changes
return updated
payload = {
'requestBean': {
'numDossier': family_id,
'unitPersonDayInfoList': bookings_to_update,
}
}
response = self.call('Activity', 'updatePersonSchedule', **payload)
if response.get('result') is False:
raise APIError(' ; '.join(x['errorMessage'] for x in response['unitPersonDayInfoErrorList']))
# sort changes
activity_types = ['ACCMAT', 'RESTSCOL']
updated = [
(
not u['booked'],
activity_types.index(u['activity_type']) if u['activity_type'] in activity_types else 0,
u['activity_label'],
u['day'],
u,
)
for u in updated
]
updated = sorted(updated, key=itemgetter(0, 1, 2, 3))
updated = [u for b, a, l, d, u in updated]
updated = [
{
'booked': u['booked'],
'activity_id': u['activity_id'],
'activity_label': u['activity_label'],
'day': u['day'],
}
for u in updated
]
return {
'updated': True,
'count': len(updated),
'changes': updated,
}
@endpoint(
display_category='Facture',
description="Ajouter une autorisation de prélèvement",
name='add-rl1-direct-debit-order',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={
'request_body': {'schema': {'application/json': invoice_schemas.ADD_DIRECT_DEBIT_ORDER_SCHEMA}}
},
)
def add_rl1_direct_debit_order(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
family = self.get_family_raw(family_id)
post_data['numPerson'] = family['RL1']['num']
self.call('Invoice', 'addDirectDebitOrder', numDossier=family_id, **post_data)
return {'data': 'ok'}
@endpoint(
display_category='Facture',
description="Obtenir les informations d'autorisation de prélèvement en cours à la date de référence",
name='get-rl1-direct-debit-order',
perm='can_access',
parameters={
'codeRegie': {'description': 'Code de la régie'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'dateRef': {
'description': 'Date de référence',
'type': 'date',
},
},
)
def get_rl1_direct_debit_order(self, request, codeRegie, dateRef, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
family = self.get_family_raw(family_id)
payload = {
'numDossier': family_id,
'numPerson': family['RL1']['num'],
'codeRegie': codeRegie,
'dateRef': dateRef,
}
response = self.call('Invoice', 'getDirectDebitOrder', **payload)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Lister les années scolaires",
name='read-school-years-list',
perm='can_access',
)
def read_school_years_list(self, request):
return {'data': self.get_referential('YearSchool')}
@endpoint(
display_category='Inscriptions',
description="Lister les niveaux scolaires",
name='read-school-levels-list',
perm='can_access',
parameters={
'age': {'description': 'Âge de l\'enfant', 'example_value': '6'},
},
)
def read_school_levels_list(self, request, age=None):
data = self.get_referential('Level')
if age and age.isnumeric():
return {'data': [item for item in data if item.get('age') == int(age)]}
return {'data': data}
@endpoint(
display_category='Inscriptions',
description="Lister les motifs de dérogation",
name='read-exemption-reasons-list',
perm='can_access',
)
def read_exemption_reasons_list(self, request):
return {'data': self.get_referential('DerogReason')}
@endpoint(
display_category='Inscriptions',
description="Lister les écoles pour une adresse et niveau scolaire",
name='read-schools-for-address-and-level',
perm='can_access',
parameters={
'year': {'description': 'Année', 'example_value': '2022'},
'id_street': {'description': 'Identifiant de la voie', 'example_value': '2317'},
'num': {'description': 'Numéro dans la voie', 'example_value': '4'},
'comp': {'description': 'Complément d\'adresse (bis, ...)'},
'level': {'description': 'Niveau scolaire'},
},
)
def read_schools_for_address_and_level(self, request, id_street, year, num, comp=None, level=None):
data = {'schoolYear': year, 'adresse': {'idStreet': id_street, 'num': num}}
if level:
data['levelCode'] = level
if comp:
data['adresse']['numComp'] = comp
response = self.call(
'Site', 'readSchoolForAdressAndLevel', readSchoolForAdressAndLevelRequestBean=data
)
data = []
for item in response:
item['id'] = item['idSchool']
item['text'] = item['schoolName']
data.append(item)
return {'data': data}
@endpoint(
display_category='Inscriptions',
description="Lister les écoles pour un enfant et niveau scolaire",
name='read-schools-for-child-and-level',
perm='can_access',
parameters={
'year': {'description': 'Année', 'example_value': '2023'},
'child_id': {'description': 'Identifiant de l\'enfant', 'example_value': '190115'},
'level': {'description': 'Niveau scolaire'},
},
)
def read_schools_for_child_and_level(self, request, child_id, year, level=None):
data = {
'numPerson': child_id,
'schoolYear': year,
}
if level:
data['levelCode'] = level
response = self.call('Family', 'readSchoolForChildAndLevel', **data)
data = []
for item in response:
item['id'] = item['idSchool']
item['text'] = item['schoolName']
data.append(item)
return {'data': data}
@endpoint(
display_category='Inscriptions',
description="Remonter les informations scolaires d'un enfant",
name='read-child-school-informations',
perm='can_access',
parameters={
'child_id': {'description': 'Identifiant de l\'enfant', 'example_value': '190115'},
'level': {'description': 'Niveau scolaire', 'example_value': 'CP'},
'year': {'description': 'Année scolaire', 'example_value': '2023'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_child_school_informations(self, request, child_id, level, year, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
data = {'numDossier': family_id, 'numPerson': child_id, 'schoolYear': year, 'level': level}
response = self.call(
'Family', 'getChildSubscribeSchoolInformation', getFamilySubscribeSchoolInfoRequestBean=data
)
if response['childSubscribeSchoolInformation'].get('subscribeSchoolInformation'):
schools = response['childSubscribeSchoolInformation']['subscribeSchoolInformation'].get(
'derogSchoolList'
)
for item in schools or []:
item['id'] = item['idSchool']
item['text'] = item['schoolName']
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Créer une pré-inscription scolaire pour un enfant",
name='create-child-school-pre-registration',
perm='can_access',
post={
'request_body': {'schema': {'application/json': family_schemas.SCHOOL_PRE_REGISTRATION_SCHEMA}}
},
)
def create_child_school_pre_registration(self, request, post_data):
response = self.call('Family', 'preSubscribeSchoolPerim', **post_data)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Créer une pré-inscription scolaire avec demande de dérogation",
name='create-child-school-pre-registration-with-exemption',
perm='can_access',
post={
'request_body': {
'schema': {'application/json': family_schemas.SCHOOL_PRE_REGISTRATION_WITH_EXEMPTION_SCHEMA}
}
},
)
def create_child_school_pre_registration_with_exemption(self, request, post_data):
response = self.call('Family', 'presubscribeSchoolDerog', **post_data)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Créer une pré-inscription scolaire avec rapprochement de fratrie",
name='create-child-school-pre-registration-with-sibling',
perm='can_access',
post={
'request_body': {
'schema': {'application/json': family_schemas.SCHOOL_PRE_REGISTRATION_WITH_SIBLING_SCHEMA}
}
},
)
def create_child_school_pre_registration_with_sibling(self, request, post_data):
response = self.call('Family', 'presubscribeSchoolSibling', **post_data)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Obtenir le catalogue des activités loisir, avec leurs critères de recherche",
name='read-activity-list',
perm='can_access',
parameters={
'ref_date': {
'description': "Date de référence, utilisée pour déduire l'année scolaire",
'type': 'date',
},
},
)
def read_activity_list(self, request, ref_date):
reference_year = utils.get_reference_year_from_date(ref_date)
labels = {
'nature': "Nature de l'activité",
'type': "Type de l'activité",
'public': 'Public',
'day': 'Jours',
}
day_names = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
all_criterias = {key: {'text': value, 'data': {}} for key, value in labels.items()}
criterias = {key: {'text': value, 'data': {}} for key, value in labels.items()}
# do not use cache, except on timeout
try:
response = self.get_activity_catalog_raw(reference_year)
except RequestException:
pass
else:
data = [
{
'id': str(reference_year),
'text': '%s-%s' % (reference_year, reference_year + 1),
'data': response,
}
]
self.update_referential('ActivityCatalog', data, 'id', 'text', delete=False)
catalogs = self.get_referential('ActivityCatalog', id=reference_year)
activities = catalogs[0]['data'] if catalogs else []
def add_criteria(label_key, criteria_key, criteria_value):
criterias[label_key]['data'][criteria_key] = criteria_value
if criteria_key not in all_criterias[label_key]['data']:
all_criterias[label_key]['data'][criteria_key] = criteria_value
def update_criterias_order_field(criterias_dict, label_keys=None):
if not label_keys:
label_keys = criterias_dict.keys()
for label_key in label_keys:
if label_key in ('public', 'day'):
criterias_dict[label_key]['order'] = sorted(x for x in criterias_dict[label_key]['data'])
else:
criterias_dict[label_key]['order'] = [
x[0]
for x in sorted(criterias_dict[label_key]['data'].items(), key=lambda x: x[1].lower())
]
data = []
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 self.get_loisir_nature_codes():
continue
activity['id'] = activity['activityPortail']['idAct']
activity['text'] = activity['activityPortail']['libelle']
for label_key in criterias:
criterias[label_key]['data'] = {}
add_criteria('nature', activity_nature['code'], activity_nature['libelle'])
type_value = activity_type['libelle'].split('-')[0].strip()
add_criteria('type', slugify(type_value), type_value)
if activity['activityPortail']['weeklyCalendarActivityList']:
for day in activity['activityPortail']['weeklyCalendarActivityList'][0]['dayWeekInfoList']:
if day['isOpen']:
add_criteria('day', str(day['dayNum']), day_names[day['dayNum'] - 1])
update_criterias_order_field(criterias, ['nature', 'type', 'day'])
for unit in activity.pop('unitPortailList'):
unit['id'] = unit['idUnit']
unit['text'] = unit['libelle']
criterias['public']['data'] = {}
for key, value in utils.get_public_criterias(
datetime.date.today(), unit['birthDateStart'], unit['birthDateEnd']
):
add_criteria('public', key, value)
update_criterias_order_field(criterias, ['public'])
for place in unit.pop('placeList'):
place['id'] = place['id']
place['text'] = place['lib2'] or place['lib']
data.append(
{
'id': '%s-%s-%s' % (activity['id'], unit['id'], place['id']),
'text': '%s, %s, %s' % (activity['text'], unit['text'], place['text']),
'activity': activity,
'unit': unit,
'place': place,
'criterias': copy.deepcopy(criterias),
}
)
update_criterias_order_field(all_criterias)
return {
'data': data,
'meta': {
'reference_year': reference_year,
'all_criterias': all_criterias,
'all_criterias_order': ['nature', 'type', 'public', 'day'],
},
}
@endpoint(
display_category='Inscriptions',
description="Obtenir le catalogue des activités d'une personne",
name='get-person-activity-list',
perm='can_access',
parameters={
'person_id': {'description': "Numéro du responsale légal ou de l'enfant"},
'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',
},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'text_template': {
'description': "Gabarit utilisé pour la valeur text (URL encoding)",
'example_value': '{{ activity.libelle2 }}',
},
},
)
def get_person_activity_list(
self,
request,
person_id,
NameID=None,
family_id=None,
nature=None,
type_ids=None,
start_date=None,
end_date=None,
text_template=None,
):
family_id = family_id or self.get_link(NameID).family_id
reference_year = None
if start_date and end_date:
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
if not text_template:
text_template = '{{ activity.libelle2|default:activity.libelle1 }}'
response = self.get_person_activity_list_raw(
family_id,
person_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()
return {'data': response['catalogueActivityList'], 'meta': {'person': response['person']}}
@endpoint(
display_category='Inscriptions',
description="Lister les unités d'une activité pour une personne",
name='get-person-unit-list',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'person_id': {'description': "Numéro du responsale légal ou de l'enfant"},
'activity_id': {'description': "Numéro de l'activité"},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text (URL encoding)',
'example_value': '{{ libelle }}',
},
},
)
def get_person_unit_list(
self,
request,
person_id,
activity_id,
NameID=None,
family_id=None,
start_date=None,
end_date=None,
text_template=None,
):
family_id = family_id or self.get_link(NameID).family_id
reference_year = None
if start_date and end_date:
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
if not text_template:
text_template = '{{ libelle }}'
response = self.get_person_activity_list_raw(
family_id,
person_id,
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 activity in response['catalogueActivityList']:
if activity['activity']['idActivity'] == activity_id:
break
else:
raise APIError('No activity %s for person' % activity_id)
data = activity.pop('unitInfoList')
meta = {'person': response['person'], 'activity': activity}
for item in data:
item['id'] = item['idUnit']
context = dict(item)
context['meta'] = meta
item['text'] = render_to_string(text_template, context).strip()
return {'data': data, 'meta': meta}
@endpoint(
display_category='Inscriptions',
description="Lister les lieux d'une unité pour une personne",
name='get-person-place-list',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'person_id': {'description': "Numéro du responsale légal ou de l'enfant"},
'activity_id': {'description': "Numéro de l'activité"},
'unit_id': {'description': "Numéro de l'unité"},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'text_template': {
'description': 'Gabarit utilisé pour la valeur text (URL encoding)',
'example_value': '{{ libelle }}',
},
},
)
def get_person_place_list(
self,
request,
person_id,
activity_id,
unit_id,
NameID=None,
family_id=None,
start_date=None,
end_date=None,
text_template=None,
):
family_id = family_id or self.get_link(NameID).family_id
reference_year = None
if start_date and end_date:
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
if not text_template:
text_template = '{{ place.lib2|default:place.lib1 }}'
response = self.get_person_activity_list_raw(
family_id,
person_id,
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 activity in response['catalogueActivityList']:
if activity['activity']['idActivity'] == activity_id:
break
else:
raise APIError('No activity %s for person' % activity_id)
for unit in activity['unitInfoList']:
if unit['idUnit'] == unit_id:
break
else:
raise APIError('No unit %s for person' % unit_id)
data = unit.pop('placeInfoList')
del activity['unitInfoList']
meta = {'person': response['person'], 'activity': activity, 'unit': unit}
for item in data:
item['id'] = item['place']['idPlace']
context = dict(item)
context['meta'] = meta
item['text'] = render_to_string(text_template, context).strip()
return {'data': data, 'meta': meta}
@endpoint(
display_category='Inscriptions',
description="Obtenir le catalogue geojson des activités pour une personne",
name='get-person-catalog-geojson',
perm='can_access',
parameters={
'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': {
'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',
},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'activity_id': {'description': "Numéro de l'activité"},
'unit_id': {'description': "Numéro de l'unité"},
'place_id': {'description': "Numéro du lieu"},
},
)
def get_person_catalog_geojson(
self,
request,
person_id,
NameID=None,
family_id=None,
start_date=None,
end_date=None,
nature=None,
type_ids=None,
activity_id=None,
unit_id=None,
place_id=None,
):
family_id = family_id or self.get_link(NameID).family_id
reference_year = None
if start_date and end_date:
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
response = self.get_person_activity_list_raw(
family_id,
person_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),
)
def places(properties, place_list, place_id=None):
for place in place_list:
properties['place'] = place
properties['place_id'] = place['place']['idPlace']
if not place['place']['longitude'] or not place['place']['latitude']:
continue
if place_id:
if properties['place_id'] == place_id:
yield properties
break
else:
yield properties
def units(properties, unit_list, unit_id=None, place_id=None):
for unit in unit_list:
place_list = unit.pop('placeInfoList')
properties['unit'] = unit
properties['unit_id'] = unit['idUnit']
if unit_id:
if properties['unit_id'] == unit_id:
yield from places(properties, place_list, place_id)
break
else:
yield from places(properties, place_list, place_id)
def activities(activity_id=None, unit_id=None, place_id=None):
for activity in response['catalogueActivityList']:
unit_list = activity.pop('unitInfoList')
properties = {
'person': response['person'],
'activity_id': activity['activity']['idActivity'],
'activity': activity,
}
if activity_id:
if properties['activity_id'] == activity_id:
yield from units(properties, unit_list, unit_id, place_id)
break
else:
yield from units(properties, unit_list, unit_id, place_id)
geojson = {
'type': 'FeatureCollection',
'features': [],
}
for item in activities(activity_id, unit_id, place_id):
geojson['features'].append(
{
'type': 'Feature',
'geometry': {
'coordinates': [
float(item['place']['place']['longitude']),
float(item['place']['place']['latitude']),
],
'type': 'Point',
},
'properties': {
'id': '%s:%s:%s' % (item['activity_id'], item['unit_id'], item['place_id']),
'text': '%s / %s / %s'
% (
item['activity']['activity']['libelle1'],
item['unit']['libelle'],
item['place']['place']['lib1'],
),
**item,
},
}
)
return geojson
@endpoint(
display_category='Inscriptions',
description="Lister les natures des activités",
name='read-activity-nature-list',
perm='can_access',
parameters={
'nature_ids': {
'description': "Codes des natures des activités (tous par défaut), séparées par des virgules",
'example_value': 'P,1,2',
},
},
)
def read_activity_nature_list(self, request, nature_ids=None):
data = self.get_referential('ActivityNatureType')
if nature_ids:
codes = [x.strip() for x in nature_ids.split(',') if x.strip()]
data = [x for x in data if x['id'] in codes]
groups = {}
for group in data:
types = []
for item in group['activityTypeList']:
types.append(
{
'id': item['code'],
'text': item['libelle'],
**item,
}
)
groups[group['id']] = types
return {'data': data, 'meta': groups}
@endpoint(
display_category='Inscriptions',
description="Lister directions de la ville",
name='read-direction-list',
perm='can_access',
)
def read_direction_list(self, request):
return {'data': self.get_referential('Direct')}
@endpoint(
display_category='Inscriptions',
description="Lister services de la ville",
name='read-service-list',
perm='can_access',
parameters={'direction_id': {'description': "Numéro de la direction sur laquelle filtrer"}},
)
def read_service_list(self, request, direction_id=None):
queryset = self.referential.filter(referential_name='Service')
if direction_id:
queryset = queryset.filter(item_data__idDir=direction_id)
return {'data': [x.item_data for x in queryset]}
@endpoint(
display_category='Inscriptions',
description="Lister les indicateurs pour les activités petite enfance",
name='read-ape-indicators-list',
perm='can_access',
)
def read_ape_indicators_list(self, request, level=None):
data = self.get_referential('ApeIndicator')
levels = {}
for level in data:
indicators = []
for item in level['indicatorList']:
indicators.append(
{
'id': item['code'],
'text': item['libelle'],
**item,
}
)
levels[level['id']] = indicators
return {'data': data, 'meta': levels}
@endpoint(
display_category='Inscriptions',
description="Obtenir les informations pour s'inscrire puis réserver sur l'extra-scolaire ou le loisir",
name='get-person-subscription-info',
perm='can_access',
parameters={
'person_id': {'description': "Numéro du responsale légal ou de l'enfant"},
'activity_id': {'description': "Numéro de l'activité"},
'unit_id': {'description': "Numéro de l'unité"},
'place_id': {'description': "Numéro du lieu"},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
'ref_date': {'description': 'Date du début du calcul'},
},
)
def get_person_subscription_info(
self,
request,
person_id,
activity_id,
unit_id,
place_id,
NameID=None,
family_id=None,
ref_date=None,
):
family_id = family_id or self.get_link(NameID).family_id
if ref_date:
try:
ref_date = parse_date(ref_date)
except ValueError:
raise APIError('%s is not a valid date' % ref_date, http_status=400)
if not ref_date:
raise APIError('bad date format, should be YYYY-MM-DD', http_status=400)
ref_date = ref_date.strftime(utils.json_date_format)
params = {
'numDossier': family_id,
'numPerson': person_id,
'activityUnitPlace': {
'idActivity': activity_id,
'idUnit': unit_id,
'idPlace': place_id,
},
'dateRef': ref_date,
}
response = self.call('Activity', 'getPersonUnitInfo', getPersonUnitInfoRequestBean=params)
day_names = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
recurrent_week = []
weekly_calendar = response['weeklyCalendarActivity']
if response['calendarGeneration'].get('value') in ('O', 'F') and weekly_calendar:
units = []
for item in response.get('unitScheduleList') or []:
key = item['unit']['calendarLetter']
value = item['unit']['libelle']
units.append((key, value))
for item in weekly_calendar.get('dayWeekInfoList') or []:
if item['isOpen']:
day_num = item['dayNum']
day = day_names[day_num - 1]
for key, value in units:
recurrent_week.append(
{
'id': '%s-%s' % (day_num, key),
'day': day,
'label': value,
'overlaps': ['%s-%s' % (day_num, k) for k, v in units if k != key],
'text': '%s %s' % (day, value),
}
)
response['recurrent_week'] = recurrent_week
if response.get('conveyance'):
for part_of_day in response['conveyance'].values():
for bus_stop in part_of_day['depositPlaceList'] or []:
place = bus_stop['place']
bus_stop['id'] = place['id']
bus_stop['text'] = place['lib2'] or place['lib']
indicators = {}
for item in response.get('indicatorList') or []:
item['id'] = item['code']
item['text'] = item['libelle']
indicators[item['id']] = item
response['indicators'] = indicators
bookings = []
many_units = len(response.get('unitScheduleList') or []) > 1
for day in response.get('openDayList') or []:
day_str = day['day'].strftime(utils.json_date_format)
for unit in response.get('unitScheduleList') or []:
if not unit['unit']['dateDeb'] <= day['day'] <= unit['unit']['dateFin']:
continue
booking = {
'id': '%s:%s:%s:%s' % (person_id, activity_id, unit['unit']['idUnit'], day_str),
'text': dateformat.format(day['day'], 'l j F Y'),
'prefill': False,
'disabled': day['hasPlace'] is False,
'details': day,
}
booking['details']['day_str'] = day_str
booking['details']['status_color'] = 'white'
booking['details']['activity_label'] = unit['unit']['libelle'] if many_units else ''
# uncheck other units selected on same day
booking['details']['activity_id'] = unit['unit']['idUnit']
bookings.append(copy.deepcopy(booking))
# sort bookings
bookings = [
(
b['details']['day'],
b['details']['activity_label'],
b,
)
for b in bookings
]
bookings = sorted(bookings, key=itemgetter(0, 1))
bookings = [b for d, l, b in bookings]
response['agenda'] = bookings
return {'data': response}
def process_subscription_payload(self, family_id, post_data):
params = {
'numDossier': family_id,
'numPerson': post_data['person_id'],
'activityUnitPlace': {
'idActivity': post_data['activity_id'],
'idUnit': post_data['unit_id'],
'idPlace': post_data['place_id'],
},
}
subscription_info = self.call('Activity', 'getPersonUnitInfo', getPersonUnitInfoRequestBean=params)
if post_data.get('conveyanceSubscribe') and (
post_data['conveyanceSubscribe'].get('idPlaceMorning')
or post_data['conveyanceSubscribe'].get('idPlaceAfternoon')
):
if not subscription_info.get('conveyance'):
raise APIError('no conveyance defined on this activity')
for payload_key, info_key in [
('idPlaceMorning', 'morningJourney'),
('idPlaceAfternoon', 'afternoonJourney'),
]:
if post_data['conveyanceSubscribe'].get(payload_key):
info_bus_ids = [
x['place']['id']
for x in subscription_info['conveyance'][info_key]['depositPlaceList']
]
payload_bus_id = post_data['conveyanceSubscribe'][payload_key]
if not payload_bus_id in info_bus_ids:
raise APIError(
'no "%s" place defined on "%s" conveyance' % (payload_bus_id, info_key)
)
recurrent_week = []
for item in post_data.get('recurrent_week') or []:
day_num, key = item.split('-')
recurrent_week.append(
{
'dayNum': day_num,
'calendarLetter': key,
'isPresent': True,
}
)
return recurrent_week
@endpoint(
display_category='Inscriptions',
description="Ajouter au panier une inscription extra-scolaire ou loisir",
name='add-person-basket-subscription',
perm='can_access',
post={
'request_body': {
'schema': {
'application/json': activity_schemas.SUBSCRIPTION_SCHEMA,
}
}
},
)
def add_person_basket_subscription(
self,
request,
post_data,
NameID=None,
family_id=None,
):
family_id = family_id or self.get_link(NameID).family_id
recurrent_week = self.process_subscription_payload(family_id, post_data)
payload = {
'addPersonUnitBasketRequestBean': {
'numFamily': family_id,
'numPerson': post_data['person_id'],
'idAct': post_data['activity_id'],
'idUnit': post_data['unit_id'],
'idPlace': post_data['place_id'],
'dateStartSubscribe': post_data['start_date'],
'dateEndSubscribe': post_data['end_date'],
'dayWeekInfoList': recurrent_week,
'conveyanceSubscribe': post_data.get('conveyanceSubscribe'),
}
}
response = self.call('Activity', 'addPersonUnitBasket', **payload)
if not response['controlResult']['controlOK']:
raise APIError(response['controlResult']['message'])
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Ajouter une inscription extra-scolaire ou loisir",
name='add-person-subscription',
perm='can_access',
post={
'request_body': {
'schema': {
'application/json': activity_schemas.SUBSCRIPTION_SCHEMA,
}
}
},
)
def add_person_subscription(
self,
request,
post_data,
NameID=None,
family_id=None,
):
family_id = family_id or self.get_link(NameID).family_id
recurrent_week = self.process_subscription_payload(family_id, post_data)
payload = {
'AddPersonSubscribeRequestBean': {
'numFamily': family_id,
'numPerson': post_data['person_id'],
'idAct': post_data['activity_id'],
'idUnit': post_data['unit_id'],
'idPlace': post_data['place_id'],
'dateStartSubscribe': post_data['start_date'],
'dateEndSubscribe': post_data['end_date'],
'dayWeekInfoList': recurrent_week,
'conveyanceSubscribe': post_data.get('conveyanceSubscribe'),
}
}
response = self.call('Activity', 'addPersonUnitSubscribe', **payload)
if not response['controlOK']:
raise APIError(response['message'])
return {'data': response}
def get_activity_bookings(self, family_id, person_id, activity_id, start_date, end_date):
bookings = []
for booking_date in rrule.rrule(rrule.MONTHLY, dtstart=start_date.replace(day=1), until=end_date):
payload = {
'requestBean': {
'numDossier': family_id,
'numPerson': person_id,
'year': booking_date.year,
'month': booking_date.month,
'idAct': activity_id,
}
}
response = self.call('Activity', 'getPersonScheduleList', **payload)
for result_data in response or []:
for schedule in result_data['activityScheduleList']:
activity = schedule['activity']
if activity['idAct'] != activity_id:
continue
many_units = len(schedule['unitScheduleList'] or []) > 1
for unit in schedule['unitScheduleList'] or []:
for day in unit['dayInfoList'] or []:
if day['status'] in ['NO_READ', 'NO_CUSTODY']:
continue
booking = {
'id': '%s:%s:%s:%s'
% (
person_id,
activity_id,
unit['unit']['idUnit'],
day['day'].strftime(utils.json_date_format),
),
'text': dateformat.format(day['day'], 'l j F Y'),
'prefill': day['scheduledPresence'] > 0,
'disabled': day['status'] != 'WRITABLE',
'details': day,
}
color = 'white'
if booking['prefill']:
color = 'green'
booking['details']['day_str'] = day['day'].strftime(utils.json_date_format)
booking['details']['status_color'] = color
booking['details']['activity_label'] = (
unit['unit']['libelle'] if many_units else ''
)
# uncheck other sub-units selected on same day
booking['details']['activity_id'] = unit['unit']['idUnit']
# use to call maelis for booking
booking['details']['maelis_activity_id'] = activity_id
booking['details']['maelis_unit_id'] = unit['unit']['idUnit']
# use to compare from maelis errors messages
booking['details']['maelis_day_str'] = day['day'].strftime('%d/%m/%Y')
booking['details']['maelis_unit_label'] = unit['unit']['libelle']
bookings.append(copy.deepcopy(booking))
# sort bookings
bookings = [
(
b['details']['day'],
b['details']['activity_label'],
b,
)
for b in bookings
]
bookings = sorted(bookings, key=itemgetter(0, 1))
bookings = [b for d, l, b in bookings]
return bookings
@endpoint(
display_category='Réservation',
description="Obtenir l'agenda d'une activité",
name='read-activity-agenda',
perm='can_access',
parameters={
'person_id': {'description': "Numéro du responsale légal ou de l'enfant"},
'activity_id': {'description': "Numéro de l'activité"},
'start_date': {'description': 'Début de la période'},
'end_date': {'description': 'Fin de la période'},
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def read_activity_agenda(
self, request, person_id, activity_id, start_date, end_date, NameID=None, family_id=None
):
family_id = family_id or self.get_link(NameID).family_id
start_date, end_date, reference_year = self.get_start_and_end_dates(start_date, end_date)
bookings = self.get_activity_bookings(family_id, person_id, activity_id, start_date, end_date)
return {
'data': bookings,
'extra_data': {
'start_date': start_date,
'end_date': end_date,
'school_year': '%s/%s' % (reference_year, reference_year + 1),
},
}
@endpoint(
display_category='Réservation',
description="Modifier l'agenda d'un enfant",
name='update-activity-agenda',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={
'request_body': {
'schema': {
'application/json': activity_schemas.BOOKING_ACTIVITY_SCHEMA,
}
}
},
)
def update_activity_agenda(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
person_id = post_data['person_id']
activity_id = post_data['activity_id']
start_date, end_date, dummy = self.get_start_and_end_dates(
post_data['start_date'], post_data['end_date']
)
requested_bookings = post_data['booking_list']
# build list of existing booked days
bookings = self.get_activity_bookings(family_id, person_id, activity_id, start_date, end_date)
legacy_bookings = [b['id'] for b in bookings if b['prefill'] is True]
available_bookings = [b['id'] for b in bookings if b['disabled'] is False]
updated = {}
bookings_to_update = []
for booking_info in bookings:
day_id = booking_info['id']
booked = None
action = booking_info['details']['action']
if day_id not in available_bookings:
# disabled or not available: not bookable
booked = None
elif (
day_id not in legacy_bookings
and day_id in requested_bookings
and action in ['ADD_PRES_PREVI']
):
booked = action
elif (
day_id in legacy_bookings
and day_id not in requested_bookings
and action in ['DEL_PRES_PREVI']
):
booked = action
if booked is not None:
# no changes, don't send the day
bookings_to_update.append(
{
'numPerson': person_id,
'idAct': booking_info['details']['maelis_activity_id'],
'idUni': booking_info['details']['maelis_unit_id'],
'date': booking_info['details']['day_str'],
'action': booked,
}
)
updated[day_id] = {
'activity_label': booking_info['details']['activity_label'],
'day': booking_info['details']['day_str'],
'booked': booked in ['ADD_PRES_PREVI'],
}
if not bookings_to_update:
# don't call maelis if no changes
return {'updated': False, 'count': 0, 'changes': [], 'errors': []}
payload = {
'requestBean': {
'numDossier': family_id,
'unitPersonDayInfoList': bookings_to_update,
}
}
response = self.call('Activity', 'updatePersonSchedule', **payload)
# booking errors from maelis
if response.get('result') is False:
for error in response['unitPersonDayInfoErrorList']:
day_id = '%s:%s:%s:%s' % (
error['unitPersonDayInfoBean']['numPerson'],
error['unitPersonDayInfoBean']['idAct'],
error['unitPersonDayInfoBean']['idUni'],
error['unitPersonDayInfoBean']['date'].strftime(utils.json_date_format),
)
if updated.get(day_id):
del updated[day_id]
changes = sorted(updated.values(), key=lambda x: (x['booked'], x['activity_label'], x['day']))
return {
'updated': True,
'count': len(changes),
'changes': changes,
}
@endpoint(
display_category='Inscriptions',
description="Obtenir les paniers de la famille",
name='get-baskets',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
)
def get_baskets(self, request, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
baskets = self.get_baskets_raw(family_id)
for item in baskets:
item['text'] = self.get_referential_value('Regie', item['codeRegie'])
return {'data': baskets}
@endpoint(
display_category='Inscriptions',
description="Prolonger la durée de vie du panier",
name='update-basket-time',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': activity_schemas.BASKET_SCHEMA}}},
)
def update_basket_time(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.get_basket_raw(family_id, post_data['basket_id'])
self.call('Activity', 'updateBasketTime', idBasket=post_data['basket_id'])
return {'data': 'ok'}
@endpoint(
display_category='Inscriptions',
description="Supprimer une ligne du panier",
name='delete-basket-line',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': activity_schemas.BASKET_LINE_SCHEMA}}},
)
def delete_basket_line(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
basket = self.get_basket_raw(family_id, post_data['basket_id'])
for line in basket['lignes']:
if line['id'] == post_data['line_id']:
break
else:
raise APIError("no '%s' basket line on basket" % post_data['line_id'])
response = self.call(
'Activity',
'deletePersonUnitBasket',
deletePersonUnitBasketRequestBean={
'idBasketLine': post_data['line_id'],
},
)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Supprimer le panier de la famille",
name='delete-basket',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': activity_schemas.BASKET_SCHEMA}}},
)
def delete_basket(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.get_basket_raw(family_id, post_data['basket_id'])
self.call(
'Activity',
'deleteBasket',
deleteBasketRequestBean={
'idBasket': post_data['basket_id'],
'idUtilisat': NameID or 'Middle-office',
},
)
return {'data': 'ok'}
@endpoint(
display_category='Inscriptions',
description="Valider le panier de la famille",
name='validate-basket',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'family_id': {'description': 'Numéro de DUI'},
},
post={'request_body': {'schema': {'application/json': activity_schemas.BASKET_SCHEMA}}},
)
def validate_basket(self, request, post_data, NameID=None, family_id=None):
family_id = family_id or self.get_link(NameID).family_id
self.get_basket_raw(family_id, post_data['basket_id'])
response = self.call(
'Activity',
'validateBasket',
validateBasketRequestBean={
'idBasket': post_data['basket_id'],
},
)
return {'data': response}
@endpoint(
display_category='Inscriptions',
description="Lister les crèches",
name='read-nursery-list',
perm='can_access',
parameters={
'activity_type': {'description': "Type de l'activité.", 'example_value': 'CRECHCO'},
'code_psu': {'description': 'Code PSU.', 'example_value': 'REGULAR'},
},
)
def read_nursery_list(self, request, activity_type=None, code_psu=None):
nurseries = self.get_referential('Nursery')
if activity_type:
nurseries = [n for n in nurseries if n['activityType']['code'] == activity_type]
if code_psu:
nurseries = [n for n in nurseries if code_psu in [u['typeAcc'] for u in n['unitList']]]
for item in nurseries:
item['activity_id'] = item['idActivity']
item['place_id'] = item['place']['idPlace']
item['unit_ids'] = {}
for unit in item['unitList'] or []:
item['unit_ids'][unit['typeAcc']] = unit['idUnit']
if code_psu:
item['unit_id'] = item['unit_ids'].get(code_psu)
return {'data': nurseries}
@endpoint(
display_category='Inscriptions',
description="Obtenir un geojson avec la liste des crèches",
name='get-nursery-geojson',
perm='can_access',
parameters={
'activity_type': {'description': "Type de l'activité.", 'example_value': 'CRECHCO'},
'code_psu': {'description': 'Code PSU. (REGULAR par défaut)'},
},
)
def get_nursery_geojson(self, request, activity_type=None, code_psu='REGULAR'):
nurseries = self.get_referential('Nursery')
geojson = {
'type': 'FeatureCollection',
'features': [],
}
for item in nurseries:
if activity_type and item['activityType']['code'] != activity_type:
continue
if not item['place']['longitude'] or not item['place']['latitude']:
continue
item['activity_id'] = item['idActivity']
item['place_id'] = item['place']['idPlace']
for unit in item['unitList'] or []:
if code_psu and unit['typeAcc'] != code_psu:
continue
item['unit_id'] = unit['idUnit']
geojson['features'].append(
{
'type': 'Feature',
'geometry': {
'coordinates': [
float(item['place']['longitude']),
float(item['place']['latitude']),
],
'type': 'Point',
},
'properties': {
**item,
'id': '%s:%s:%s' % (item['activity_id'], item['unit_id'], item['place_id']),
'text': unit['libelle'],
'unit': unit,
},
}
)
return geojson
@endpoint(
display_category='Inscriptions',
description="Créer une demande de place en crèche pour un enfant",
name='create-nursery-demand',
perm='can_access',
post={'request_body': {'schema': {'application/json': family_schemas.NURSERY_DEMAND_SCHEMA}}},
)
def create_nursery_demand(self, request, post_data):
apeIndicators = self.get_referential('ApeIndicator')
for group in apeIndicators:
if group['id'] == 'INDI_APE_ENF':
key = 'child_indicators'
elif group['id'] == 'INDI_APE_FAM':
key = 'family_indicators'
else:
key = 'demand_indicators'
expected_codes = [x['code'] for x in group['indicatorList']]
for i, item in enumerate(post_data.get(key) or []):
if item['code'] not in expected_codes:
raise APIError(
"%s/%i/code key value '%s' do not belong to APE '%s' indicators"
% (key, i, item['code'], group['id'][-3:])
)
item['isActive'] = self.encode_bool(item['isActive'])
child_data = {}
if post_data.get('child_id'):
child_data['numPerson'] = post_data['child_id']
else:
child_data.update(
{
'firstname': post_data.get('child_first_name'),
'lastname': post_data.get('child_last_name'),
'sexe': post_data.get('child_gender'),
'birth': {'dateBirth': post_data.get('child_birthdate')},
}
)
if post_data.get('child_indicators'):
child_data['indiPersList'] = post_data['child_indicators']
book_data = {'dateDepot': now().strftime(utils.json_date_format), 'datStart': post_data['start_date']}
if post_data.get('number_of_days'):
book_data['nbDayByWeek'] = post_data.get('number_of_days')
for day in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'):
if post_data.get('start_hour_%s' % day):
book_data['startHour%s' % day] = post_data.get('start_hour_%s' % day)
if post_data.get('end_hour_%s' % day):
book_data['endHour%s' % day] = post_data.get('end_hour_%s' % day)
if 'accept_other_nurseries' in post_data:
book_data['isAcceptOtherNursery'] = bool(post_data['accept_other_nurseries'])
if post_data.get('comment'):
book_data['description'] = post_data['comment']
if post_data.get('comment'):
book_data['description'] = post_data['comment']
if post_data.get('demand_indicators'):
book_data['indiResapeList'] = post_data['demand_indicators']
for i in range(1, 4):
if post_data.get('nursery%i' % i):
book_data['choice%s' % i] = post_data['nursery%s' % i]
data = {
'numDossier': post_data['family_id'],
'child': child_data,
'apeBook': book_data,
}
if post_data.get('family_indicators'):
data['indiFamList'] = post_data['family_indicators']
return {'data': self.call('Ape', 'addApeBook', request=data)}
@endpoint(
display_category='Facture',
description="Lister les régies",
name='read-regie-list',
perm='can_access',
)
def read_regie_list(self, request):
return {'data': self.get_referential('Regie')}
class Link(models.Model):
resource = models.ForeignKey(ToulouseMaelis, on_delete=models.CASCADE)
name_id = models.CharField(blank=False, max_length=256)
family_id = models.CharField(blank=False, max_length=128)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('resource', 'name_id')
class Referential(models.Model):
resource = models.ForeignKey(
verbose_name='Resource',
to=ToulouseMaelis,
on_delete=models.CASCADE,
related_name='referential',
)
referential_name = models.TextField('Name')
item_id = models.TextField('Key')
item_text = models.TextField('Text')
item_unaccent_text = models.TextField('Text', null=True)
item_data = JSONField('Data', encoder=DjangoJSONEncoder)
created = models.DateTimeField('Created', auto_now_add=True)
updated = models.DateTimeField('Updated', auto_now=True)
def __repr__(self):
return '<Referential "%s/%s">' % (self.referential_name, self.item_id)
class Meta:
ordering = ('resource', 'referential_name', 'item_text', 'item_id')
unique_together = [['resource', 'referential_name', 'item_id']]