Projet

Général

Profil

0002-toulouse-axel-post-annual-bookings-41405.patch

Lauréline Guérin, 16 avril 2020 16:02

Télécharger (19,2 ko)

Voir les différences:

Subject: [PATCH 2/2] toulouse-axel: post annual bookings (#41405)

 passerelle/contrib/toulouse_axel/models.py  |  85 ++++++++
 passerelle/contrib/toulouse_axel/schemas.py |  67 +++++++
 tests/test_toulouse_axel.py                 | 203 ++++++++++++++++++++
 3 files changed, 355 insertions(+)
passerelle/contrib/toulouse_axel/models.py
1157 1157
            }
1158 1158
        }
1159 1159

  
1160
    @endpoint(
1161
        description=_("CLAE/Cantine annual booking"),
1162
        perm='can_access',
1163
        parameters={
1164
            'NameID': {'description': _('Publik ID')},
1165
        },
1166
        post={
1167
            'request_body': {
1168
                'schema': {
1169
                    'application/json': schemas.ANNUAL_BOOKING_SCHEMA,
1170
                }
1171
            }
1172
        })
1173
    def clae_booking_annual(self, request, NameID, post_data):
1174
        link = self.get_link(NameID)
1175

  
1176
        # build dates of the period
1177
        today = datetime.date.today()
1178
        reference_year = utils.get_reference_year_from_date(today)
1179
        start_date = today + datetime.timedelta(days=8)
1180
        end_date = datetime.date(reference_year+1, 7, 31)
1181

  
1182
        # get known activities for this child, to have the ids
1183
        child_activities_info = self.get_child_activities(link.dui, reference_year, post_data['child_id'])
1184
        child_known_activities_by_type = {a['TYPEACTIVITE']: a for a in child_activities_info.get('ACTIVITE', [])}
1185

  
1186
        # build activity list to post
1187
        activities_by_type = {}
1188
        for activity_type in ['MAT', 'MIDI', 'SOIR', 'GARD']:
1189
            if post_data['booking_list_%s' % activity_type] is None:
1190
                # exclude if None (not updated)
1191
                continue
1192
            if activity_type not in child_known_activities_by_type:
1193
                # exclude activity types not registered for the child
1194
                continue
1195
            week_pattern = ''
1196
            activity_id = child_known_activities_by_type[activity_type]['IDACTIVITE']
1197
            # cross days of the week to find bookings
1198
            for i, day in enumerate(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']):
1199
                key = '{}:{}:{}:{}'.format(post_data['child_id'], activity_type, activity_id, day)
1200
                week_pattern += key in post_data['booking_list_%s' % activity_type] and '1' or '0'
1201
            activities_by_type[activity_type] = {
1202
                'IDACTIVITE': activity_id,
1203
                'ANNEEREFERENCE': str(reference_year),
1204
                'PERIODE': [{
1205
                    'DATEDEBUT': start_date.strftime(utils.json_date_format),
1206
                    'DATEDFIN': end_date.strftime(utils.json_date_format),
1207
                    'SEMAINETYPE': week_pattern,
1208
                }],
1209
            }
1210

  
1211
        # build data
1212
        data = {
1213
            'IDDUI': link.dui,
1214
            'DATEDEMANDE': today.strftime(utils.json_date_format),
1215
            'ENFANT': [
1216
                {
1217
                    'IDPERSONNE': post_data['child_id'],
1218
                    'REGIME': post_data.get('regime') or child_activities_info['REGIME'],
1219
                }
1220
            ]
1221
        }
1222
        for activity_type in ['MAT', 'MIDI', 'SOIR', 'GARD']:
1223
            if activity_type in activities_by_type:
1224
                if 'ACTIVITE' not in data['ENFANT'][0]:
1225
                    data['ENFANT'][0]['ACTIVITE'] = []
1226
                data['ENFANT'][0]['ACTIVITE'].append(activities_by_type[activity_type])
1227

  
1228
        try:
1229
            result = schemas.reservation_annuelle(self, {'PORTAIL': {'DUI': data}})
1230
        except AxelError as e:
1231
            raise APIError(
1232
                'Axel error: %s' % e,
1233
                err_code='error',
1234
                data={'xml_request': e.xml_request,
1235
                      'xml_response': e.xml_response})
1236

  
1237
        return {
1238
            'updated': True,
1239
            'data': {
1240
                'xml_request': result.xml_request,
1241
                'xml_response': result.xml_response,
1242
            }
1243
        }
1244

  
1160 1245

  
1161 1246
class Link(models.Model):
1162 1247
    resource = models.ForeignKey(ToulouseAxel, on_delete=models.CASCADE)
passerelle/contrib/toulouse_axel/schemas.py
363 363
    'required': ['booking_start_date', 'booking_end_date', 'booking_list_MAT', 'booking_list_MIDI', 'booking_list_SOIR', 'booking_list_GARD',
364 364
                 'child_id', 'regime']
365 365
}
366

  
367

  
368
ANNUAL_BOOKING_SCHEMA = {
369
    'type': 'object',
370
    'properties': {
371
        'booking_list_MAT': {
372
            'oneOf': [
373
                {'type': 'null'},
374
                {
375
                    'type': 'array',
376
                    'items': {
377
                        'type': 'string',
378
                        'pattern': '[A-Za-z0-9]+:MAT:[A-Za-z0-9]+:(monday|tuesday|wednesday|thursday|friday)',
379
                    }
380
                }
381
            ]
382
        },
383
        'booking_list_MIDI': {
384
            'oneOf': [
385
                {'type': 'null'},
386
                {
387
                    'type': 'array',
388
                    'items': {
389
                        'type': 'string',
390
                        'pattern': '[A-Za-z0-9]+:MIDI:[A-Za-z0-9]+:(monday|tuesday|wednesday|thursday|friday)',
391
                    }
392
                }
393
            ]
394
        },
395
        'booking_list_SOIR': {
396
            'oneOf': [
397
                {'type': 'null'},
398
                {
399
                    'type': 'array',
400
                    'items': {
401
                        'type': 'string',
402
                        'pattern': '[A-Za-z0-9]+:SOIR:[A-Za-z0-9]+:(monday|tuesday|wednesday|thursday|friday)',
403
                    }
404
                }
405
            ]
406
        },
407
        'booking_list_GARD': {
408
            'oneOf': [
409
                {'type': 'null'},
410
                {
411
                    'type': 'array',
412
                    'items': {
413
                        'type': 'string',
414
                        'pattern': '[A-Za-z0-9]+:GARD:[A-Za-z0-9]+:(monday|tuesday|wednesday|thursday|friday)',
415
                    }
416
                }
417
            ]
418
        },
419
        'child_id': {
420
            'type': 'string',
421
            'minLength': 1,
422
            'maxLength': 8,
423
        },
424
        'regime': {
425
            'oneOf': [
426
                {'type': 'null'},
427
                {'type': 'string', 'enum': ['', 'SV', 'AV']}
428
            ]
429
        }
430
    },
431
    'required': ['booking_list_MAT', 'booking_list_MIDI', 'booking_list_SOIR', 'booking_list_GARD', 'child_id']
432
}
tests/test_toulouse_axel.py
154 154
    }
155 155

  
156 156

  
157
@pytest.fixture
158
def annual_booking_params():
159
    return {
160
        'booking_list_MAT': ['3535:MAT:A19P1M1:monday', '3535:MAT:A19P1M1:tuesday', '3535:MAT:A19P1M1:thursday', '3535:MAT:A19P1M1:friday'],
161
        'booking_list_MIDI': ['3535:MIDI:A19P1M2:monday', '3535:MIDI:A19P1M2:tuesday', '3535:MIDI:A19P1M2:thursday', '3535:MIDI:A19P1M2:friday'],
162
        'booking_list_SOIR': ['3535:SOIR:A19P1M3:monday'],
163
        'booking_list_GARD': ['3535:GARD:A19P1M4:wednesday'],
164
        'child_id': '3535',
165
        'regime': 'AV',
166
    }
167

  
168

  
157 169
def test_lock(app, resource):
158 170
    resp = app.get('/toulouse-axel/test/lock?key=&locker=', status=400)
159 171
    assert resp.json['err_desc'] == "key is empty"
......
3301 3313
        assert activity['PERIODE'][4]['DATEDEBUT'] == '2020-04-27'
3302 3314
        assert activity['PERIODE'][4]['DATEDFIN'] == '2020-04-30'
3303 3315
        assert activity['PERIODE'][4]['SEMAINETYPE'] == '00010'
3316

  
3317

  
3318
@freezegun.freeze_time('2019-09-01')
3319
def test_clae_booking_annual_endpoint_axel_error(app, resource, annual_booking_params):
3320
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3321
    with mock.patch('passerelle.contrib.toulouse_axel.schemas.enfants_activites') as operation:
3322
        operation.side_effect = AxelError('FooBar')
3323
        resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3324
    assert resp.json['err_desc'] == "Axel error: FooBar"
3325
    assert resp.json['err'] == 'error'
3326

  
3327
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/child_activities.xml')
3328
    with open(filepath) as xml:
3329
        content = xml.read()
3330
    with mock_getdata(content, 'EnfantsActivites'):
3331
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3332
            operation.side_effect = AxelError('FooBar')
3333
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3334
    assert resp.json['err_desc'] == "Axel error: FooBar"
3335
    assert resp.json['err'] == 'error'
3336

  
3337

  
3338
def test_clae_booking_annual_endpoint_no_result(app, resource, annual_booking_params):
3339
    resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3340
    assert resp.json['err_desc'] == "Person not found"
3341
    assert resp.json['err'] == 'not-found'
3342

  
3343

  
3344
@freezegun.freeze_time('2019-09-01')
3345
def test_clae_booking_annual_endpoint(app, resource, annual_booking_params, child_activities_data):
3346
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3347
    activities = child_activities_data['ENFANT'][0]
3348
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3349
        content = "<PORTAIL/>"
3350
        with mock_getdata(content, 'ReservationAnnuelle'):
3351
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3352
        assert resp.json['err'] == 0
3353
        assert resp.json['updated'] is True
3354
        assert 'data' in resp.json
3355
        assert 'xml_request' in resp.json['data']
3356
        assert 'xml_response' in resp.json['data']
3357

  
3358
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3359
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3360
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3361
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3362
        assert payload == {
3363
            'DATEDEMANDE': '2019-09-01',
3364
            'ENFANT': [{
3365
                'IDPERSONNE': '3535',
3366
                'REGIME': 'AV',
3367
                'ACTIVITE': [{
3368
                    'ANNEEREFERENCE': '2019',
3369
                    'IDACTIVITE': 'A19P1M1',
3370
                    'PERIODE': [
3371
                        {
3372
                            'DATEDEBUT': '2019-09-09',
3373
                            'DATEDFIN': '2020-07-31',
3374
                            'SEMAINETYPE': '11011'
3375
                        }
3376
                    ]
3377
                }, {
3378
                    'ANNEEREFERENCE': '2019',
3379
                    'IDACTIVITE': 'A19P1M2',
3380
                    'PERIODE': [
3381
                        {
3382
                            'DATEDEBUT': '2019-09-09',
3383
                            'DATEDFIN': '2020-07-31',
3384
                            'SEMAINETYPE': '11011'
3385
                        }
3386
                    ]
3387
                }, {
3388
                    'ANNEEREFERENCE': '2019',
3389
                    'IDACTIVITE': 'A19P1M3',
3390
                    'PERIODE': [
3391
                        {
3392
                            'DATEDEBUT': '2019-09-09',
3393
                            'DATEDFIN': '2020-07-31',
3394
                            'SEMAINETYPE': '10000'
3395
                        }
3396
                    ]
3397
                }, {
3398
                    'ANNEEREFERENCE': '2019',
3399
                    'IDACTIVITE': 'A19P1M4',
3400
                    'PERIODE': [
3401
                        {
3402
                            'DATEDEBUT': '2019-09-09',
3403
                            'DATEDFIN': '2020-07-31',
3404
                            'SEMAINETYPE': '00100'
3405
                        }
3406
                    ]
3407
                }]
3408
            }],
3409
            'IDDUI': 'XXX',
3410
        }
3411

  
3412
        # empty list
3413
        new_booking_params = copy.deepcopy(annual_booking_params)
3414
        new_booking_params['booking_list_MAT'] = ['42:MAT:A19P1M1:monday']  # wrong child, ignored
3415
        new_booking_params['booking_list_MIDI'] = []
3416
        new_booking_params['booking_list_SOIR'] = []
3417
        new_booking_params['booking_list_GARD'] = []
3418
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3419
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3420
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3421
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3422
        assert len(payload['ENFANT']) == 1
3423
        assert payload['ENFANT'][0]['IDPERSONNE'] == '3535'
3424
        assert len(payload['ENFANT'][0]['ACTIVITE']) == 4
3425
        activity1 = payload['ENFANT'][0]['ACTIVITE'][0]
3426
        assert activity1['IDACTIVITE'] == 'A19P1M1'
3427
        assert len(activity1['PERIODE']) == 1
3428
        assert activity1['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3429
        assert activity1['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3430
        assert activity1['PERIODE'][0]['SEMAINETYPE'] == '00000'
3431
        activity2 = payload['ENFANT'][0]['ACTIVITE'][1]
3432
        assert activity2['IDACTIVITE'] == 'A19P1M2'
3433
        assert len(activity2['PERIODE']) == 1
3434
        assert activity2['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3435
        assert activity2['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3436
        assert activity2['PERIODE'][0]['SEMAINETYPE'] == '00000'
3437
        activity3 = payload['ENFANT'][0]['ACTIVITE'][2]
3438
        assert activity3['IDACTIVITE'] == 'A19P1M3'
3439
        assert len(activity3['PERIODE']) == 1
3440
        assert activity3['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3441
        assert activity3['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3442
        assert activity3['PERIODE'][0]['SEMAINETYPE'] == '00000'
3443
        activity4 = payload['ENFANT'][0]['ACTIVITE'][3]
3444
        assert activity4['IDACTIVITE'] == 'A19P1M4'
3445
        assert len(activity4['PERIODE']) == 1
3446
        assert activity4['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3447
        assert activity4['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3448
        assert activity4['PERIODE'][0]['SEMAINETYPE'] == '00000'
3449

  
3450
        # null list
3451
        new_booking_params = copy.deepcopy(annual_booking_params)
3452
        new_booking_params['booking_list_MAT'] = None
3453
        new_booking_params['booking_list_MIDI'] = None
3454
        new_booking_params['booking_list_SOIR'] = None
3455
        new_booking_params['booking_list_GARD'] = None
3456
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3457
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3458
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3459
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3460
        assert 'ACTIVITE' not in payload['ENFANT'][0]
3461

  
3462

  
3463
def test_clae_booking_annual_reference_year(app, resource, annual_booking_params, child_activities_data):
3464
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3465
    activities = child_activities_data['ENFANT'][0]
3466
    with freezegun.freeze_time('2020-04-16'):
3467
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3468
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3469
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3470
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3471
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3472
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-04-24'
3473
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3474
    with freezegun.freeze_time('2020-08-01'):
3475
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3476
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3477
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3478
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3479
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3480
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-08-09'
3481
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2021-07-31'
3482

  
3483

  
3484
@freezegun.freeze_time('2019-09-01')
3485
def test_clae_booking_annual_regime(app, resource, annual_booking_params, child_activities_data):
3486
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3487
    activities = child_activities_data['ENFANT'][0]
3488
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3489
        annual_booking_params['regime'] = None
3490
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3491
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3492
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3493
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3494
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3495
        annual_booking_params['regime'] = ''
3496
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3497
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3498
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3499
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3500
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3501
        del annual_booking_params['regime']
3502
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3503
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3504
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3505
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3506
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3304
-