toulouse-maelis: booking endpoints (#72774) #19

Merged
lguerin merged 2 commits from wip/72774-toulouse-maelis-bookings into main 2023-01-05 16:37:14 +01:00
7 changed files with 1172 additions and 1 deletions

View File

@ -0,0 +1,47 @@
# Copyright (C) 2023 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/>.
BOOKING_SCHEMA = {
'type': 'object',
'properties': {
'child_id': {
'type': 'string',
'minLength': 1,
'maxLength': 8,
},
'booking_list': {
'type': 'array',
'items': {
'type': 'string',
'pattern': '[A-Za-z0-9]+:[- A-Za-z0-9]+:[0-9]{4}-[0-9]{2}-[0-9]{2}',
},
},
'start_date': {
'type': 'string',
'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
},
'end_date': {
'type': 'string',
'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
},
},
'required': [
'child_id',
'booking_list',
'start_date',
'end_date',
],
}

View File

@ -14,12 +14,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import datetime
from operator import itemgetter
from urllib.parse import urljoin
import zeep
from dateutil import rrule
from django.contrib.postgres.fields import JSONField
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.utils import dateformat
from django.utils.timezone import now
from zeep.helpers import serialize_object
from zeep.wsse.username import UsernameToken
@ -30,7 +34,7 @@ from passerelle.utils.conversion import simplify
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.templates import render_to_string
from . import family_schemas, invoice_schemas, schemas
from . import activity_schemas, family_schemas, invoice_schemas, schemas, utils
class UpdateError(Exception):
@ -1613,6 +1617,231 @@ class ToulouseMaelis(BaseResource, HTTPResource):
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', err_code='bad-request', http_status=400)
if start_date > end_date:
raise APIError(
'start_date should be before end_date',
err_code='bad-request',
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),
err_code='bad-request',
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)
result = serialize_object(response)
for result_data in result or []:
lguerin marked this conversation as resolved Outdated

Visiblement Sigec renvoie une réponse vide plutôt qu'une liste vide quand un enfant n'a pas d'inscription (ou de calendrier).
Je mettrais :
for result_data in result or []:

Visiblement Sigec renvoie une réponse vide plutôt qu'une liste vide quand un enfant n'a pas d'inscription (ou de calendrier). Je mettrais : `for result_data in result or []:`
for schedule in result_data['activityScheduleList']:
activity = schedule['activity']
if activity['activityType']['natureSpec']['code'] not in ['A', 'R']:

Les seuls codes que j'ai pu manipuler avec les données que j'ai. Si on en a d'autres, les activités associées seront ignorées, il faudra mettre cette liste à jour.

Les seuls codes que j'ai pu manipuler avec les données que j'ai. Si on en a d'autres, les activités associées seront ignorées, il faudra mettre cette liste à jour.
continue
activity_id = activity['idAct']
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',
'details': day,
}
color = 'white'
if booking['prefill']:
color = 'green'

Avec ce qu'on a comme info (scheduledPresence et/ou realPresence), seulement 2 couleurs possibles (blanc=non réservé, vert=réservé)
S'il en faut plus, il me faut le mode d'emploi :)

Avec ce qu'on a comme info (scheduledPresence et/ou realPresence), seulement 2 couleurs possibles (blanc=non réservé, vert=réservé) S'il en faut plus, il me faut le mode d'emploi :)
booking['details']['status_color'] = color
booking['details']['activity_id'] = activity_id
booking['details']['activity_type'] = activity['activityType']['code']
booking['details']['activity_label'] = activity['activityType']['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']

Pour le moment, les seuls types d'activités que j'ai pu manipuler avec les données que j'ai. Au pire, si on tombe sur un nouveau type, le tri sera un peu moisi, mais ça sera juste un pb d'affichage.

Pour le moment, les seuls types d'activités que j'ai pu manipuler avec les données que j'ai. Au pire, si on tombe sur un nouveau type, le tri sera un peu moisi, mais ça sera juste un pb d'affichage.
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="Agenda d'un enfant",
name='read-child-agenda',
perm='can_access',
parameters={
'NameID': {'description': 'Publik NameID'},
'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'},
},
)
def read_child_agenda(self, request, NameID, child_id, start_date, end_date):
family_id = 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'},
},
post={
'request_body': {
'schema': {
'application/json': activity_schemas.BOOKING_SCHEMA,
}
}
},
)
def update_child_agenda(self, request, NameID, post_data):
family_id = 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

J'applique l'action renvoyée par le WS getPersonScheduleList, en vérifiant quand meme qu'elle est pertinente

J'applique l'action renvoyée par le WS getPersonScheduleList, en vérifiant quand meme qu'elle est pertinente

test la phrase pas terminée elle s'arrête.

test la phrase pas terminée elle s'arrête.
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

idem

idem
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)
errors = serialize_object(response)
if errors:
raise APIError(' ; '.join(errors), err_code='agenda-child-error')
# 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="Ajout d'autorisation de prélèvement",

View File

@ -0,0 +1,23 @@
# Copyright (C) 2023 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/>.
json_date_format = '%Y-%m-%d'
def get_reference_year_from_date(some_date):
if some_date.month <= 8:
# between january and august, reference year is the year just before
return some_date.year - 1
return some_date.year

View File

@ -0,0 +1,250 @@
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getPersonScheduleListResponse
xmlns:ns2="activity.ws.maelis.sigec.com"
xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com">
<resultBean>
<personScheduleList>
<person>
<numPerson>613880</numPerson>
<lastname>DOE</lastname>
<firstname>JANNIS</firstname>
</person>
<activityScheduleList>
<activity>
<idAct>A10049327689</idAct>
<libelle>CLAE MATIN 22/23</libelle>
<activityType>
<code>ACCMAT</code>
<libelle>Accueil du matin</libelle>
<natureSpec>
<code>A</code>
<libelle>Accueil P\xc3\xa9riscolaire</libelle>
</natureSpec>
</activityType>
</activity>
<unitScheduleList>
<unit>
<idUnit>A10049327690</idUnit>
<libelle>CLAE MATIN 22/23</libelle>
</unit>
<dayInfoList>
<day>2023-01-01T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-02T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>READ_ONLY</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-03T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>READ_ONLY</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-04T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-05T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_ABSENCE</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-06T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_ABSENCE</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-07T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-08T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-09T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-10T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-11T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-12T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-13T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-14T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-15T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
</unitScheduleList>
</activityScheduleList>
<activityScheduleList>
<activity>
<idAct>A10049327682</idAct>
<libelle>RESTAURATION SCOLAIRE 22/23</libelle>
<activityType>
<code>RESTSCOL</code>
<libelle>Restauration scolaire</libelle>
<natureSpec>
<code>R</code>
<libelle>Restauration Scolaire</libelle>
</natureSpec>
</activityType>
</activity>
<unitScheduleList>
<unit>
<idUnit>A10049327683</idUnit>
<libelle>RESTAURATION SCOLAIRE 22/23</libelle>
</unit>
<dayInfoList>
<day>2023-01-01T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-02T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>READ_ONLY</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-03T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>READ_ONLY</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-04T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-05T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_ABSENCE</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-06T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_REAL</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-07T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-08T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-09T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-10T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-11T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-12T00:00:00+01:00</day>
<scheduledPresence>1</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>DEL_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-13T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>WRITABLE</ns3:status>
<ns3:action>ADD_PRES_PREVI</ns3:action>
</dayInfoList>
<dayInfoList>
<day>2023-01-14T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
<dayInfoList>
<day>2023-01-15T00:00:00+01:00</day>
<scheduledPresence>0</scheduledPresence>
<realPresence>0</realPresence>
<ns3:status>NO_READ</ns3:status>
</dayInfoList>
</unitScheduleList>
</activityScheduleList>
</personScheduleList>
</resultBean>
</ns2:getPersonScheduleListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,10 @@
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:updatePersonScheduleResponse
xmlns:ns2="activity.ws.maelis.sigec.com"
xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com">
<resultBean/>
</ns2:updatePersonScheduleResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,13 @@
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:updatePersonScheduleResponse
xmlns:ns2="activity.ws.maelis.sigec.com"
xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com">
<resultBean>
<errorMessages>Foo</errorMessages>
<errorMessages>Bar</errorMessages>
</resultBean>
</ns2:updatePersonScheduleResponse>
</soap:Body>
</soap:Envelope>

View File

@ -3698,3 +3698,602 @@ def test_read_exemption_reasons_list(con, app):
for item in resp.json['data']:
assert 'id' in item
assert 'text' in item
def test_read_child_agenda(activity_service, con, app):
activity_service.add_soap_response(
'getPersonScheduleList', get_xml_file('R_get_person_schedule_list.xml')
)
url = get_endpoint('read-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.get(url + '?NameID=local&child_id=613880&start_date=2023-01-01&end_date=2023-01-15')
assert resp.json['err'] == 0
assert resp.json['extra_data'] == {
'start_date': '2023-01-01',
'end_date': '2023-01-15',
'school_year': '2022/2023',
}
assert len(resp.json['data']) == 16
assert resp.json['data'] == [
{
'details': {
'absence': None,
'action': None,
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-02T00:00:00+01:00',
'day_str': '2023-01-02',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': True,
'id': '613880:A10049327689:2023-01-02',
'prefill': False,
'text': 'Monday 2 January 2023',
},
{
'details': {
'absence': None,
'action': None,
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-02T00:00:00+01:00',
'day_str': '2023-01-02',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': True,
'id': '613880:A10049327682:2023-01-02',
'prefill': False,
'text': 'Monday 2 January 2023',
},
{
'details': {
'absence': None,
'action': None,
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-03T00:00:00+01:00',
'day_str': '2023-01-03',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': True,
'id': '613880:A10049327689:2023-01-03',
'prefill': False,
'text': 'Tuesday 3 January 2023',
},
{
'details': {
'absence': None,
'action': None,
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-03T00:00:00+01:00',
'day_str': '2023-01-03',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'READ_ONLY',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': True,
'id': '613880:A10049327682:2023-01-03',
'prefill': True,
'text': 'Tuesday 3 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_ABSENCE',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-05T00:00:00+01:00',
'day_str': '2023-01-05',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-05',
'prefill': True,
'text': 'Thursday 5 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_ABSENCE',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-05T00:00:00+01:00',
'day_str': '2023-01-05',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-05',
'prefill': True,
'text': 'Thursday 5 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_ABSENCE',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-06T00:00:00+01:00',
'day_str': '2023-01-06',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-06',
'prefill': True,
'text': 'Friday 6 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_PRES_REAL',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-06T00:00:00+01:00',
'day_str': '2023-01-06',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-06',
'prefill': False,
'text': 'Friday 6 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_PRES_PREVI',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-09T00:00:00+01:00',
'day_str': '2023-01-09',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-09',
'prefill': False,
'text': 'Monday 9 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_PRES_PREVI',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-09T00:00:00+01:00',
'day_str': '2023-01-09',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-09',
'prefill': False,
'text': 'Monday 9 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_PRES_PREVI',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-10T00:00:00+01:00',
'day_str': '2023-01-10',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-10',
'prefill': False,
'text': 'Tuesday 10 January 2023',
},
{
'details': {
'absence': None,
'action': 'DEL_PRES_PREVI',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-10T00:00:00+01:00',
'day_str': '2023-01-10',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-10',
'prefill': True,
'text': 'Tuesday 10 January 2023',
},
{
'details': {
'absence': None,
'action': 'DEL_PRES_PREVI',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-12T00:00:00+01:00',
'day_str': '2023-01-12',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-12',
'prefill': True,
'text': 'Thursday 12 January 2023',
},
{
'details': {
'absence': None,
'action': 'DEL_PRES_PREVI',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-12T00:00:00+01:00',
'day_str': '2023-01-12',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-12',
'prefill': True,
'text': 'Thursday 12 January 2023',
},
{
'details': {
'absence': None,
'action': 'DEL_PRES_PREVI',
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'activity_type': 'ACCMAT',
'child_id': '613880',
'day': '2023-01-13T00:00:00+01:00',
'day_str': '2023-01-13',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-13',
'prefill': True,
'text': 'Friday 13 January 2023',
},
{
'details': {
'absence': None,
'action': 'ADD_PRES_PREVI',
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'activity_type': 'RESTSCOL',
'child_id': '613880',
'day': '2023-01-13T00:00:00+01:00',
'day_str': '2023-01-13',
'hasPlace': None,
'realPresence': 0,
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-13',
'prefill': False,
'text': 'Friday 13 January 2023',
},
]
def test_read_child_agenda_not_linked_error(con, app):
url = get_endpoint('read-child-agenda')
resp = app.get(url + '?NameID=local&child_id=613880&start_date=2022-09-01&end_date=2023-08-31')
assert resp.json['err'] == 'not-linked'
assert resp.json['err_desc'] == 'User not linked to family'
def test_read_child_agenda_date_error(con, app):
url = get_endpoint('read-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.get(url + '?NameID=local&child_id=613880&start_date=bad&end_date=2023-08-31', status=400)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'bad date format, should be YYYY-MM-DD'
resp = app.get(url + '?NameID=local&child_id=613880&start_date=2022-09-01&end_date=bad', status=400)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'bad date format, should be YYYY-MM-DD'
resp = app.get(
url + '?NameID=local&child_id=613880&start_date=2023-09-01&end_date=2023-08-31', status=400
)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'start_date should be before end_date'
resp = app.get(
url + '?NameID=local&child_id=613880&start_date=2022-09-01&end_date=2024-08-31', status=400
)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'start_date and end_date are in different reference year (2022 != 2023)'
def test_update_child_agenda(activity_service, con, app):
activity_service.add_soap_response(
'getPersonScheduleList', get_xml_file('R_get_person_schedule_list.xml')
)
url = get_endpoint('update-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
def request_check(request):
assert request.numDossier == 1312
assert len(request.unitPersonDayInfoList) == 4
assert request.unitPersonDayInfoList[0].numPerson == 613880
assert request.unitPersonDayInfoList[0].idAct == 'A10049327689'
assert request.unitPersonDayInfoList[0].idUni == 'A10049327690'
assert request.unitPersonDayInfoList[0].date == datetime.datetime(2023, 1, 5, 0, 0)
assert request.unitPersonDayInfoList[0].action == 'ADD_ABSENCE'
assert request.unitPersonDayInfoList[1].numPerson == 613880
assert request.unitPersonDayInfoList[1].idAct == 'A10049327682'
assert request.unitPersonDayInfoList[1].idUni == 'A10049327683'
assert request.unitPersonDayInfoList[1].date == datetime.datetime(2023, 1, 6, 0, 0)
assert request.unitPersonDayInfoList[1].action == 'ADD_PRES_REAL'
assert request.unitPersonDayInfoList[2].numPerson == 613880
assert request.unitPersonDayInfoList[2].idAct == 'A10049327682'
assert request.unitPersonDayInfoList[2].idUni == 'A10049327683'
assert request.unitPersonDayInfoList[2].date == datetime.datetime(2023, 1, 10, 0, 0)
assert request.unitPersonDayInfoList[2].action == 'DEL_PRES_PREVI'
assert request.unitPersonDayInfoList[3].numPerson == 613880
assert request.unitPersonDayInfoList[3].idAct == 'A10049327682'
assert request.unitPersonDayInfoList[3].idUni == 'A10049327683'
assert request.unitPersonDayInfoList[3].date == datetime.datetime(2023, 1, 13, 0, 0)
assert request.unitPersonDayInfoList[3].action == 'ADD_PRES_PREVI'
activity_service.add_soap_response(
'updatePersonSchedule', get_xml_file('R_update_person_schedule.xml'), request_check=request_check
)
params = {
'child_id': '613880',
'start_date': '2023-01-01',
'end_date': '2023-01-15',
'booking_list': [
# '613880:A10049327689:2023-01-05', # remove this one
'613880:A10049327682:2023-01-05',
'613880:A10049327689:2023-01-06',
# '613880:A10049327682:2023-01-10', # and this one
'613880:A10049327689:2023-01-12',
'613880:A10049327682:2023-01-12',
'613880:A10049327689:2023-01-13',
# but add:
'613880:A10049327682:2023-01-06',
'613880:A10049327682:2023-01-13',
],
}
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json == {
'changes': [
{
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'booked': True,
'day': '2023-01-06',
},
{
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'booked': True,
'day': '2023-01-13',
},
{
'activity_id': 'A10049327689',
'activity_label': 'Accueil du matin',
'booked': False,
'day': '2023-01-05',
},
{
'activity_id': 'A10049327682',
'activity_label': 'Restauration scolaire',
'booked': False,
'day': '2023-01-10',
},
],
'count': 4,
'err': 0,
'updated': True,
}
def test_update_child_agenda_no_changes(activity_service, con, app):
activity_service.add_soap_response(
'getPersonScheduleList', get_xml_file('R_get_person_schedule_list.xml')
)
url = get_endpoint('update-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
params = {
'child_id': '613880',
'start_date': '2023-01-01',
'end_date': '2023-01-15',
'booking_list': [
'613880:A10049327689:2023-01-05',
'613880:A10049327682:2023-01-05',
'613880:A10049327689:2023-01-06',
'613880:A10049327682:2023-01-10',
'613880:A10049327689:2023-01-12',
'613880:A10049327682:2023-01-12',
'613880:A10049327689:2023-01-13',
],
}
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json == []
def test_update_child_agenda_maelis_error(activity_service, con, app):
activity_service.add_soap_response(
'getPersonScheduleList', get_xml_file('R_get_person_schedule_list.xml')
)
url = get_endpoint('update-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
activity_service.add_soap_response(
'updatePersonSchedule', get_xml_file('R_update_person_schedule_error.xml')
)
params = {
'child_id': '613880',
'start_date': '2023-01-01',
'end_date': '2023-01-15',
'booking_list': [
'613880:A10049327682:2023-01-13',
],
}
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 'agenda-child-error'
assert resp.json['err_desc'] == 'Foo ; Bar'
def test_update_child_agenda_not_linked_error(con, app):
url = get_endpoint('update-child-agenda')
params = {
'child_id': '613880',
'start_date': '2022-09-01',
'end_date': '2023-08-31',
'booking_list': [],
}
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 'not-linked'
assert resp.json['err_desc'] == 'User not linked to family'
def test_update_child_agenda_date_error(con, app):
url = get_endpoint('update-child-agenda')
Link.objects.create(resource=con, family_id='1312', name_id='local')
params = {
'child_id': '613880',
'start_date': 'bad',
'end_date': '2023-08-31',
'booking_list': [],
}
resp = app.post_json(url + '?NameID=local', params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "start_date: 'bad' does not match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'"
params = {
'child_id': '613880',
'start_date': '2022-09-01',
'end_date': 'bad',
'booking_list': [],
}
resp = app.post_json(url + '?NameID=local', params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "end_date: 'bad' does not match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'"
params = {
'child_id': '613880',
'start_date': '2023-09-01',
'end_date': '2023-08-31',
'booking_list': [],
}
resp = app.post_json(url + '?NameID=local', params=params, status=400)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'start_date should be before end_date'
params = {
'child_id': '613880',
'start_date': '2022-09-01',
'end_date': '2024-08-31',
'booking_list': [],
}
resp = app.post_json(url + '?NameID=local', params=params, status=400)
assert resp.json['err'] == 'bad-request'
assert resp.json['err_desc'] == 'start_date and end_date are in different reference year (2022 != 2023)'