Projet

Général

Profil

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

Lauréline Guérin, 17 avril 2020 09:34

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
1147 1147
            }
1148 1148
        }
1149 1149

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

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

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

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

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

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

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

  
1150 1235

  
1151 1236
class Link(models.Model):
1152 1237
    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"
......
3223 3235
            app.post_json('/toulouse-axel/test/clae_booking?NameID=yyy', params=booking_params)
3224 3236
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3225 3237
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3238

  
3239

  
3240
@freezegun.freeze_time('2019-09-01')
3241
def test_clae_booking_annual_endpoint_axel_error(app, resource, annual_booking_params):
3242
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3243
    with mock.patch('passerelle.contrib.toulouse_axel.schemas.enfants_activites') as operation:
3244
        operation.side_effect = AxelError('FooBar')
3245
        resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3246
    assert resp.json['err_desc'] == "Axel error: FooBar"
3247
    assert resp.json['err'] == 'error'
3248

  
3249
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/child_activities.xml')
3250
    with open(filepath) as xml:
3251
        content = xml.read()
3252
    with mock_getdata(content, 'EnfantsActivites'):
3253
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3254
            operation.side_effect = AxelError('FooBar')
3255
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3256
    assert resp.json['err_desc'] == "Axel error: FooBar"
3257
    assert resp.json['err'] == 'error'
3258

  
3259

  
3260
def test_clae_booking_annual_endpoint_no_result(app, resource, annual_booking_params):
3261
    resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3262
    assert resp.json['err_desc'] == "Person not found"
3263
    assert resp.json['err'] == 'not-found'
3264

  
3265

  
3266
@freezegun.freeze_time('2019-09-01')
3267
def test_clae_booking_annual_endpoint(app, resource, annual_booking_params, child_activities_data):
3268
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3269
    activities = child_activities_data['ENFANT'][0]
3270
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3271
        content = "<PORTAIL/>"
3272
        with mock_getdata(content, 'ReservationAnnuelle'):
3273
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3274
        assert resp.json['err'] == 0
3275
        assert resp.json['updated'] is True
3276
        assert 'data' in resp.json
3277
        assert 'xml_request' in resp.json['data']
3278
        assert 'xml_response' in resp.json['data']
3279

  
3280
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3281
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3282
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3283
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3284
        assert payload == {
3285
            'DATEDEMANDE': '2019-09-01',
3286
            'ENFANT': [{
3287
                'IDPERSONNE': '3535',
3288
                'REGIME': 'AV',
3289
                'ACTIVITE': [{
3290
                    'ANNEEREFERENCE': '2019',
3291
                    'IDACTIVITE': 'A19P1M1',
3292
                    'PERIODE': [
3293
                        {
3294
                            'DATEDEBUT': '2019-09-09',
3295
                            'DATEDFIN': '2020-07-31',
3296
                            'SEMAINETYPE': '11011'
3297
                        }
3298
                    ]
3299
                }, {
3300
                    'ANNEEREFERENCE': '2019',
3301
                    'IDACTIVITE': 'A19P1M2',
3302
                    'PERIODE': [
3303
                        {
3304
                            'DATEDEBUT': '2019-09-09',
3305
                            'DATEDFIN': '2020-07-31',
3306
                            'SEMAINETYPE': '11011'
3307
                        }
3308
                    ]
3309
                }, {
3310
                    'ANNEEREFERENCE': '2019',
3311
                    'IDACTIVITE': 'A19P1M3',
3312
                    'PERIODE': [
3313
                        {
3314
                            'DATEDEBUT': '2019-09-09',
3315
                            'DATEDFIN': '2020-07-31',
3316
                            'SEMAINETYPE': '10000'
3317
                        }
3318
                    ]
3319
                }, {
3320
                    'ANNEEREFERENCE': '2019',
3321
                    'IDACTIVITE': 'A19P1M4',
3322
                    'PERIODE': [
3323
                        {
3324
                            'DATEDEBUT': '2019-09-09',
3325
                            'DATEDFIN': '2020-07-31',
3326
                            'SEMAINETYPE': '00100'
3327
                        }
3328
                    ]
3329
                }]
3330
            }],
3331
            'IDDUI': 'XXX',
3332
        }
3333

  
3334
        # empty list
3335
        new_booking_params = copy.deepcopy(annual_booking_params)
3336
        new_booking_params['booking_list_MAT'] = ['42:MAT:A19P1M1:monday']  # wrong child, ignored
3337
        new_booking_params['booking_list_MIDI'] = []
3338
        new_booking_params['booking_list_SOIR'] = []
3339
        new_booking_params['booking_list_GARD'] = []
3340
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3341
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3342
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3343
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3344
        assert len(payload['ENFANT']) == 1
3345
        assert payload['ENFANT'][0]['IDPERSONNE'] == '3535'
3346
        assert len(payload['ENFANT'][0]['ACTIVITE']) == 4
3347
        activity1 = payload['ENFANT'][0]['ACTIVITE'][0]
3348
        assert activity1['IDACTIVITE'] == 'A19P1M1'
3349
        assert len(activity1['PERIODE']) == 1
3350
        assert activity1['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3351
        assert activity1['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3352
        assert activity1['PERIODE'][0]['SEMAINETYPE'] == '00000'
3353
        activity2 = payload['ENFANT'][0]['ACTIVITE'][1]
3354
        assert activity2['IDACTIVITE'] == 'A19P1M2'
3355
        assert len(activity2['PERIODE']) == 1
3356
        assert activity2['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3357
        assert activity2['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3358
        assert activity2['PERIODE'][0]['SEMAINETYPE'] == '00000'
3359
        activity3 = payload['ENFANT'][0]['ACTIVITE'][2]
3360
        assert activity3['IDACTIVITE'] == 'A19P1M3'
3361
        assert len(activity3['PERIODE']) == 1
3362
        assert activity3['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3363
        assert activity3['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3364
        assert activity3['PERIODE'][0]['SEMAINETYPE'] == '00000'
3365
        activity4 = payload['ENFANT'][0]['ACTIVITE'][3]
3366
        assert activity4['IDACTIVITE'] == 'A19P1M4'
3367
        assert len(activity4['PERIODE']) == 1
3368
        assert activity4['PERIODE'][0]['DATEDEBUT'] == '2019-09-09'
3369
        assert activity4['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3370
        assert activity4['PERIODE'][0]['SEMAINETYPE'] == '00000'
3371

  
3372
        # null list
3373
        new_booking_params = copy.deepcopy(annual_booking_params)
3374
        new_booking_params['booking_list_MAT'] = None
3375
        new_booking_params['booking_list_MIDI'] = None
3376
        new_booking_params['booking_list_SOIR'] = None
3377
        new_booking_params['booking_list_GARD'] = None
3378
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3379
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3380
            resp = app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=new_booking_params)
3381
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3382
        assert 'ACTIVITE' not in payload['ENFANT'][0]
3383

  
3384

  
3385
def test_clae_booking_annual_reference_year(app, resource, annual_booking_params, child_activities_data):
3386
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
3387
    activities = child_activities_data['ENFANT'][0]
3388
    with freezegun.freeze_time('2020-04-16'):
3389
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3390
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3391
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3392
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3393
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3394
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-04-24'
3395
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2020-07-31'
3396
    with freezegun.freeze_time('2020-08-01'):
3397
        with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3398
            with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3399
                operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3400
                app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3401
    payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3402
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDEBUT'] == '2020-08-09'
3403
    assert payload['ENFANT'][0]['ACTIVITE'][0]['PERIODE'][0]['DATEDFIN'] == '2021-07-31'
3404

  
3405

  
3406
@freezegun.freeze_time('2019-09-01')
3407
def test_clae_booking_annual_regime(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 mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_child_activities', return_value=activities):
3411
        annual_booking_params['regime'] = None
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]['REGIME'] == 'SV'
3417
        annual_booking_params['regime'] = ''
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
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3421
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3422
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3423
        del annual_booking_params['regime']
3424
        with mock.patch('passerelle.contrib.toulouse_axel.schemas.reservation_annuelle') as operation:
3425
            operation.return_value = OperationResult(json_response={}, xml_request='', xml_response='')
3426
            app.post_json('/toulouse-axel/test/clae_booking_annual?NameID=yyy', params=annual_booking_params)
3427
        payload = operation.call_args_list[0][0][1]['PORTAIL']['DUI']
3428
        assert payload['ENFANT'][0]['REGIME'] == 'SV'
3226
-