Projet

Général

Profil

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

Lauréline Guérin, 17 avril 2020 15:20

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
1149 1149
            }
1150 1150
        }
1151 1151

  
1152
    @endpoint(
1153
        description=_("CLAE/Cantine annual booking"),
1154
        perm='can_access',
1155
        parameters={
1156
            'NameID': {'description': _('Publik ID')},
1157
        },
1158
        post={
1159
            'request_body': {
1160
                'schema': {
1161
                    'application/json': schemas.ANNUAL_BOOKING_SCHEMA,
1162
                }
1163
            }
1164
        })
1165
    def clae_booking_annual(self, request, NameID, post_data):
1166
        link = self.get_link(NameID)
1167

  
1168
        # build dates of the period
1169
        today = datetime.date.today()
1170
        reference_year = utils.get_reference_year_from_date(today)
1171
        start_date = today + datetime.timedelta(days=8)
1172
        end_date = datetime.date(reference_year+1, 7, 31)
1173

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

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

  
1203
        # build data
1204
        data = {
1205
            'IDDUI': link.dui,
1206
            'DATEDEMANDE': today.strftime(utils.json_date_format),
1207
            'ENFANT': [
1208
                {
1209
                    'IDPERSONNE': post_data['child_id'],
1210
                    'REGIME': post_data.get('regime') or child_activities_info['REGIME'],
1211
                }
1212
            ]
1213
        }
1214
        for activity_type in ['MAT', 'MIDI', 'SOIR', 'GARD']:
1215
            if activity_type in activities_by_type:
1216
                if 'ACTIVITE' not in data['ENFANT'][0]:
1217
                    data['ENFANT'][0]['ACTIVITE'] = []
1218
                data['ENFANT'][0]['ACTIVITE'].append(activities_by_type[activity_type])
1219

  
1220
        try:
1221
            result = schemas.reservation_annuelle(self, {'PORTAIL': {'DUI': data}})
1222
        except AxelError as e:
1223
            raise APIError(
1224
                'Axel error: %s' % e,
1225
                err_code='error',
1226
                data={'xml_request': e.xml_request,
1227
                      'xml_response': e.xml_response})
1228

  
1229
        return {
1230
            'updated': True,
1231
            'data': {
1232
                'xml_request': result.xml_request,
1233
                'xml_response': result.xml_response,
1234
            }
1235
        }
1236

  
1152 1237

  
1153 1238
class Link(models.Model):
1154 1239
    resource = models.ForeignKey(ToulouseAxel, on_delete=models.CASCADE)
passerelle/contrib/toulouse_axel/schemas.py
358 358
    },
359 359
    'required': ['booking_start_date', 'booking_end_date', 'booking_list_MAT', 'booking_list_MIDI', 'booking_list_SOIR', 'booking_list_GARD', 'child_id']
360 360
}
361

  
362

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

  
157 157

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

  
169

  
158 170
def test_lock(app, resource):
159 171
    resp = app.get('/toulouse-axel/test/lock?key=&locker=', status=400)
160 172
    assert resp.json['err_desc'] == "key is empty"
......
3245 3257
            app.post_json('/toulouse-axel/test/clae_booking?NameID=yyy', params=booking_params)
3246 3258
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3247 3259
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3260

  
3261

  
3262
@freezegun.freeze_time('2019-09-01')
3263
def test_clae_booking_annual_endpoint_axel_error(app, resource, annual_booking_params):
3264
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3265
    with mock.patch('passerelle.contrib.toulouse_axel.schemas.enfants_activites') as operation:
3266
        operation.side_effect = AxelError('FooBar')
3267
        resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3268
    assert resp.json['err_desc'] == "Axel error: FooBar"
3269
    assert resp.json['err'] == 'error'
3270

  
3271
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/child_activities.xml')
3272
    with open(filepath) as xml:
3273
        content = xml.read()
3274
    with mock_getdata(content, 'EnfantsActivites'):
3275
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3276
            operation.side_effect = AxelError('FooBar')
3277
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3278
    assert resp.json['err_desc'] == "Axel error: FooBar"
3279
    assert resp.json['err'] == 'error'
3280

  
3281

  
3282
def test_clae_booking_annual_endpoint_no_result(app, resource, annual_booking_params):
3283
    resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3284
    assert resp.json['err_desc'] == "Person not found"
3285
    assert resp.json['err'] == 'not-found'
3286

  
3287

  
3288
@freezegun.freeze_time('2019-09-01')
3289
def test_clae_booking_annual_endpoint(app, resource, annual_booking_params, child_activities_data):
3290
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3291
    activities = child_activities_data['ENFANT'][0]
3292
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3293
        content = "<PORTAIL/>"
3294
        with mock_getdata(content, 'ReservationAnnuelle'):
3295
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3296
        assert resp.json['err'] == 0
3297
        assert resp.json['updated'] is True
3298
        assert 'data' in resp.json
3299
        assert 'xml_request' in resp.json['data']
3300
        assert 'xml_response' in resp.json['data']
3301

  
3302
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3303
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3304
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3305
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3306
        assert payload == {
3307
            'DATEDEMANDE': '2019-09-01',
3308
            'ENFANT': [{
3309
                'IDPERSONNE': '3535',
3310
                'REGIME': 'AV',
3311
                'ACTIVITE': [{
3312
                    'ANNEEREFERENCE': '2019',
3313
                    'IDACTIVITE': 'A19P1M1',
3314
                    'PERIODE': [
3315
                        {
3316
                            'DATEDEBUT': '2019-09-09',
3317
                            'DATEDFIN': '2020-07-31',
3318
                            'SEMAINETYPE': '11011'
3319
                        }
3320
                    ]
3321
                }, {
3322
                    'ANNEEREFERENCE': '2019',
3323
                    'IDACTIVITE': 'A19P1M2',
3324
                    'PERIODE': [
3325
                        {
3326
                            'DATEDEBUT': '2019-09-09',
3327
                            'DATEDFIN': '2020-07-31',
3328
                            'SEMAINETYPE': '11011'
3329
                        }
3330
                    ]
3331
                }, {
3332
                    'ANNEEREFERENCE': '2019',
3333
                    'IDACTIVITE': 'A19P1M3',
3334
                    'PERIODE': [
3335
                        {
3336
                            'DATEDEBUT': '2019-09-09',
3337
                            'DATEDFIN': '2020-07-31',
3338
                            'SEMAINETYPE': '10000'
3339
                        }
3340
                    ]
3341
                }, {
3342
                    'ANNEEREFERENCE': '2019',
3343
                    'IDACTIVITE': 'A19P1M4',
3344
                    'PERIODE': [
3345
                        {
3346
                            'DATEDEBUT': '2019-09-09',
3347
                            'DATEDFIN': '2020-07-31',
3348
                            'SEMAINETYPE': '00100'
3349
                        }
3350
                    ]
3351
                }]
3352
            }],
3353
            'IDDUI': 'XXX',
3354
        }
3355

  
3356
        # empty list
3357
        new_booking_params = copy.deepcopy(annual_booking_params)
3358
        new_booking_params['booking_list_MAT'] = ['42:MAT:A19P1M1:monday']  # wrong child, ignored
3359
        new_booking_params['booking_list_MIDI'] = []
3360
        new_booking_params['booking_list_SOIR'] = []
3361
        new_booking_params['booking_list_GARD'] = []
3362
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3363
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3364
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3365
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3366
        assert len(payload['ENFANT']) == 1
3367
        assert payload['ENFANT'][0]['IDPERSONNE'] == '3535'
3368
        assert len(payload['ENFANT'][0]['ACTIVITE']) == 4
3369
        activity1 = payload['ENFANT'][0]['ACTIVITE'][0]
3370
        assert activity1['IDACTIVITE'] == 'A19P1M1'
3371
        assert len(activity1['PERIODE']) == 1
3372
        assert activity1['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3373
        assert activity1['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3374
        assert activity1['PERIODE'][0]['SEMAINETYPE'] == '00000'
3375
        activity2 = payload['ENFANT'][0]['ACTIVITE'][1]
3376
        assert activity2['IDACTIVITE'] == 'A19P1M2'
3377
        assert len(activity2['PERIODE']) == 1
3378
        assert activity2['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3379
        assert activity2['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3380
        assert activity2['PERIODE'][0]['SEMAINETYPE'] == '00000'
3381
        activity3 = payload['ENFANT'][0]['ACTIVITE'][2]
3382
        assert activity3['IDACTIVITE'] == 'A19P1M3'
3383
        assert len(activity3['PERIODE']) == 1
3384
        assert activity3['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3385
        assert activity3['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3386
        assert activity3['PERIODE'][0]['SEMAINETYPE'] == '00000'
3387
        activity4 = payload['ENFANT'][0]['ACTIVITE'][3]
3388
        assert activity4['IDACTIVITE'] == 'A19P1M4'
3389
        assert len(activity4['PERIODE']) == 1
3390
        assert activity4['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3391
        assert activity4['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3392
        assert activity4['PERIODE'][0]['SEMAINETYPE'] == '00000'
3393

  
3394
        # null list
3395
        new_booking_params = copy.deepcopy(annual_booking_params)
3396
        new_booking_params['booking_list_MAT'] = None
3397
        new_booking_params['booking_list_MIDI'] = None
3398
        new_booking_params['booking_list_SOIR'] = None
3399
        new_booking_params['booking_list_GARD'] = None
3400
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3401
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3402
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3403
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3404
        assert 'ACTIVITE' not in payload['ENFANT'][0]
3405

  
3406

  
3407
def test_clae_booking_annual_reference_year(app, resource, annual_booking_params, child_activities_data):
3408
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3409
    activities = child_activities_data['ENFANT'][0]
3410
    with freezegun.freeze_time('2020-04-16'):
3411
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3412
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3413
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3414
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3415
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3416
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-04-24'
3417
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3418
    with freezegun.freeze_time('2020-08-01'):
3419
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3420
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3421
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3422
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3423
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3424
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-08-09'
3425
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2021-07-31'
3426

  
3427

  
3428
@freezegun.freeze_time('2019-09-01')
3429
def test_clae_booking_annual_regime(app, resource, annual_booking_params, child_activities_data):
3430
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3431
    activities = child_activities_data['ENFANT'][0]
3432
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3433
        annual_booking_params['regime'] = None
3434
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3435
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3436
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3437
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3438
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3439
        annual_booking_params['regime'] = ''
3440
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3441
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3442
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3443
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3444
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3445
        del annual_booking_params['regime']
3446
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3447
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3448
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3449
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3450
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3248
-