toulouse-maelis: update-child-agenda endpoint (#72774)
gitea-wip/passerelle/pipeline/pr-main This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-01-05 10:20:42 +01:00
parent 569764da59
commit 5c40ae304d
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
5 changed files with 397 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

@ -34,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, utils
from . import activity_schemas, family_schemas, invoice_schemas, schemas, utils
class UpdateError(Exception):
@ -1682,6 +1682,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
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
@ -1726,6 +1727,121 @@ class ToulouseMaelis(BaseResource, HTTPResource):
},
}
@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
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)
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,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

@ -3732,6 +3732,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': True,
'id': '613880:A10049327689:2023-01-02',
@ -3753,6 +3754,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': True,
'id': '613880:A10049327682:2023-01-02',
@ -3774,6 +3776,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'READ_ONLY',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': True,
'id': '613880:A10049327689:2023-01-03',
@ -3795,6 +3798,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'READ_ONLY',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': True,
'id': '613880:A10049327682:2023-01-03',
@ -3816,6 +3820,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-05',
@ -3837,6 +3842,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-05',
@ -3858,6 +3864,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-06',
@ -3879,6 +3886,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-06',
@ -3900,6 +3908,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-09',
@ -3921,6 +3930,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-09',
@ -3942,6 +3952,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-10',
@ -3963,6 +3974,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-10',
@ -3984,6 +3996,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-12',
@ -4005,6 +4018,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-12',
@ -4026,6 +4040,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 1,
'status': 'WRITABLE',
'status_color': 'green',
'unit_id': 'A10049327690',
},
'disabled': False,
'id': '613880:A10049327689:2023-01-13',
@ -4047,6 +4062,7 @@ def test_read_child_agenda(activity_service, con, app):
'scheduledPresence': 0,
'status': 'WRITABLE',
'status_color': 'white',
'unit_id': 'A10049327683',
},
'disabled': False,
'id': '613880:A10049327682:2023-01-13',
@ -4087,3 +4103,197 @@ def test_read_child_agenda_date_error(con, app):
)
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)'