Projet

Général

Profil

0001-pricing-compute-pricing-with-data-retrieved-from-chr.patch

Lauréline Guérin, 02 juin 2022 12:06

Télécharger (38,2 ko)

Voir les différences:

Subject: [PATCH] pricing: compute pricing with data retrieved from chrono
 (#65456)

 lingo/pricing/models.py      | 124 ++++++------
 lingo/settings.py            |   2 +
 tests/conftest.py            |   9 +
 tests/pricing/conftest.py    |  36 ----
 tests/pricing/test_models.py | 355 +++++++++++++----------------------
 tests/settings.py            |  11 +-
 6 files changed, 221 insertions(+), 316 deletions(-)
 delete mode 100644 tests/pricing/conftest.py
lingo/pricing/models.py
16 16

  
17 17
import copy
18 18
import dataclasses
19
import datetime
19 20
import decimal
20 21
from typing import List
21 22

  
22 23
from django.contrib.postgres.fields import JSONField
23 24
from django.db import models
24
from django.template import Context, RequestContext, Template, TemplateSyntaxError
25
from django.template import Context, RequestContext, Template, TemplateSyntaxError, VariableDoesNotExist
25 26
from django.utils.text import slugify
26 27
from django.utils.translation import ugettext_lazy as _
27 28

  
28
from lingo.agendas.models import Agenda
29
from lingo.agendas.models import Agenda, CheckType
29 30
from lingo.utils.misc import AgendaImportError, clean_import_data, generate_slug
30 31

  
31 32

  
......
51 52
    pass
52 53

  
53 54

  
54
class PricingEventNotCheckedError(PricingError):
55
class PricingUnknownCheckStatusError(PricingError):
55 56
    pass
56 57

  
57 58

  
58
class PricingBookingNotCheckedError(PricingError):
59
class PricingEventNotCheckedError(PricingError):
59 60
    pass
60 61

  
61 62

  
62
class PricingSubscriptionError(PricingError):
63
class PricingBookingNotCheckedError(PricingError):
63 64
    pass
64 65

  
65 66

  
......
203 204
        context.push(original_context)
204 205
        for key, tplt in (self.extra_variables or {}).items():
205 206
            try:
206
                template = Template(tplt)
207
            except TemplateSyntaxError:
207
                result[key] = Template(tplt).render(context)
208
            except (TemplateSyntaxError, VariableDoesNotExist):
208 209
                continue
209
            result[key] = template.render(context)
210 210
        return result
211 211

  
212 212
    def get_extra_variables_keys(self):
......
353 353
        }
354 354

  
355 355
    @staticmethod
356
    def get_pricing_data(request, event, user_external_id, adult_external_id):
357
        agenda_pricing = AgendaPricing.get_agenda_pricing(event)
358
        context = agenda_pricing.get_pricing_context(request, user_external_id, adult_external_id)
359
        pricing, criterias = agenda_pricing.compute_pricing(context)
360
        modifier = agenda_pricing.get_booking_modifier(event, user_external_id)
361
        return agenda_pricing.aggregate_pricing_data(pricing, criterias, context, modifier)
356
    def get_pricing_data(
357
        request, agenda, event, subscription, check_status, booking, user_external_id, adult_external_id
358
    ):
359
        agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
360
        data = {
361
            'event': event,
362
            'subscription': subscription,
363
            'booking': booking,
364
        }
365
        context = agenda_pricing.get_pricing_context(
366
            request=request,
367
            data=data,
368
            user_external_id=user_external_id,
369
            adult_external_id=adult_external_id,
370
        )
371
        pricing, criterias = agenda_pricing.compute_pricing(context=context)
372
        modifier = agenda_pricing.get_booking_modifier(check_status=check_status)
373
        return agenda_pricing.aggregate_pricing_data(
374
            pricing=pricing, criterias=criterias, context=context, modifier=modifier
375
        )
362 376

  
363 377
    def aggregate_pricing_data(self, pricing, criterias, context, modifier):
364 378
        if modifier['modifier_type'] == 'fixed':
......
376 390
        }
377 391

  
378 392
    @staticmethod
379
    def get_agenda_pricing(event):
380
        agenda = event.agenda
393
    def get_agenda_pricing(agenda, event):
394
        start_datetime = datetime.datetime.fromisoformat(event['start_datetime'])
381 395
        try:
382 396
            return agenda.agendapricing_set.get(
383
                date_start__lte=event.start_datetime,
384
                date_end__gt=event.start_datetime,
397
                date_start__lte=start_datetime,
398
                date_end__gt=start_datetime,
385 399
            )
386 400
        except (AgendaPricing.DoesNotExist, AgendaPricing.MultipleObjectsReturned):
387 401
            raise AgendaPricingNotFound
388 402

  
389
    def get_subscription(self, event, user_external_id):
390
        try:
391
            return self.agenda.subscriptions.get(
392
                user_external_id=user_external_id,
393
                date_start__lte=event.start_datetime,
394
                date_end__gt=event.start_datetime,
395
            )
396
        # noqa pylint: disable=undefined-variable
397
        except (Subscription.DoesNotExist, Subscription.MultipleObjectsReturned):
398
            raise PricingSubscriptionError
399

  
400
    def get_pricing_context(self, request, user_external_id, adult_external_id):
401
        context = {'user_external_id': user_external_id, 'adult_external_id': adult_external_id}
403
    def get_pricing_context(self, request, data, user_external_id, adult_external_id):
404
        context = {'data': data, 'user_external_id': user_external_id, 'adult_external_id': adult_external_id}
402 405
        if ':' in user_external_id:
403 406
            context['user_external_raw_id'] = user_external_id.split(':')[1]
404 407
        if ':' in adult_external_id:
......
442 445

  
443 446
        return pricing, criterias
444 447

  
445
    def get_booking_modifier(self, event, user_external_id):
446
        # event must be checked
447
        if event.checked is False:
448
            raise PricingEventNotCheckedError
449

  
450
        # search for an available subscription
451
        self.get_subscription(event, user_external_id)
452

  
453
        # search for a booking
454
        try:
455
            booking = event.booking_set.get(user_external_id=user_external_id)
456
        except Booking.DoesNotExist:  # noqa pylint: disable=undefined-variable
457
            # no booking
448
    def get_booking_modifier(self, check_status):
449
        status = check_status['status']
450
        if status not in ['error', 'not-booked', 'cancelled', 'presence', 'absence']:
451
            raise PricingUnknownCheckStatusError
452

  
453
        if status == 'error':
454
            reason = check_status['error_reason']
455
            # event must be checked
456
            if reason == 'event-not-checked':
457
                raise PricingEventNotCheckedError
458
            # booking must be checked
459
            if reason == 'booking-not-checked':
460
                raise PricingBookingNotCheckedError
461
            # too many bookings found
462
            if reason == 'too-many-bookings-found':
463
                raise PricingMultipleBookingError
464

  
465
        # no booking found
466
        if status == 'not-booked':
458 467
            return {
459 468
                'status': 'not-booked',
460 469
                'modifier_type': 'rate',
461 470
                'modifier_rate': 0,
462 471
            }
463
        except Booking.MultipleObjectsReturned:  # noqa pylint: disable=undefined-variable
464
            raise PricingMultipleBookingError
465 472

  
466 473
        # booking cancelled
467
        if booking.cancellation_datetime is not None:
474
        if status == 'cancelled':
468 475
            return {
469 476
                'status': 'cancelled',
470 477
                'modifier_type': 'rate',
471 478
                'modifier_rate': 0,
472 479
            }
473 480

  
474
        # booking not cancelled, is must be checked
475
        if booking.user_was_present is None:
476
            raise PricingBookingNotCheckedError
477

  
478
        status = 'presence' if booking.user_was_present else 'absence'
479 481
        # no check_type, default rates
480
        if booking.user_check_type is None:
482
        if not check_status['check_type']:
481 483
            return {
482 484
                'status': status,
483 485
                'check_type_group': None,
484 486
                'check_type': None,
485 487
                'modifier_type': 'rate',
486
                'modifier_rate': 100 if booking.user_was_present else 0,
488
                'modifier_rate': 100 if status == 'presence' else 0,
487 489
            }
488 490

  
489
        check_type = booking.user_check_type
490
        kind_mapping = {'presence': True, 'absence': False}
491
        try:
492
            check_type = CheckType.objects.get(
493
                group=self.agenda.check_type_group_id, slug=check_status['check_type']
494
            )
495
        except CheckType.DoesNotExist:
496
            raise PricingBookingCheckTypeError(
497
                details={
498
                    'reason': 'not-found',
499
                }
500
            )
491 501
        # check_type kind and user_was_present mismatch
492
        if kind_mapping[check_type.kind] != booking.user_was_present:
502
        if check_type.kind != status:
493 503
            raise PricingBookingCheckTypeError(
494 504
                details={
495 505
                    'check_type_group': check_type.group.slug,
lingo/settings.py
89 89
                'django.template.context_processors.static',
90 90
                'django.template.context_processors.tz',
91 91
                'django.contrib.messages.context_processors.messages',
92
                'publik_django_templatetags.wcs.context_processors.cards',
92 93
            ],
93 94
            'builtins': [
94 95
                'publik_django_templatetags.publik.templatetags.publik',
96
                'publik_django_templatetags.wcs.templatetags.wcs',
95 97
                'django.contrib.humanize.templatetags.humanize',
96 98
            ],
97 99
        },
tests/conftest.py
26 26
@pytest.fixture
27 27
def admin_user():
28 28
    return User.objects.create_superuser('admin', email=None, password='admin')
29

  
30

  
31
@pytest.fixture
32
def nocache(settings):
33
    settings.CACHES = {
34
        'default': {
35
            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
36
        }
37
    }
tests/pricing/conftest.py
1
import pytest
2
from django.contrib.auth.models import Group, User
3

  
4

  
5
@pytest.fixture
6
def simple_user():
7
    try:
8
        user = User.objects.get(username='user')
9
    except User.DoesNotExist:
10
        user = User.objects.create_user('user', password='user')
11
    return user
12

  
13

  
14
@pytest.fixture
15
def managers_group():
16
    group, _ = Group.objects.get_or_create(name='Managers')
17
    return group
18

  
19

  
20
@pytest.fixture
21
def manager_user(managers_group):
22
    try:
23
        user = User.objects.get(username='manager')
24
    except User.DoesNotExist:
25
        user = User.objects.create_user('manager', password='manager')
26
    user.groups.set([managers_group])
27
    return user
28

  
29

  
30
@pytest.fixture
31
def admin_user():
32
    try:
33
        user = User.objects.get(username='admin')
34
    except User.DoesNotExist:
35
        user = User.objects.create_superuser('admin', email=None, password='admin')
36
    return user
tests/pricing/test_models.py
5 5
import pytest
6 6
from django.template import Context
7 7
from django.test.client import RequestFactory
8
from django.utils.timezone import make_aware, now
8
from django.utils.timezone import make_aware
9 9
from publik_django_templatetags.wcs.context_processors import Cards
10 10

  
11 11
from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
......
26 26
    PricingMatrixCell,
27 27
    PricingMatrixRow,
28 28
    PricingMultipleBookingError,
29
    PricingSubscriptionError,
29
    PricingUnknownCheckStatusError,
30 30
)
31 31

  
32 32
pytestmark = pytest.mark.django_db
......
222 222
    assert new_pricing.slug == 'bar'
223 223

  
224 224

  
225
@pytest.mark.xfail(reason='no link to events yet')
226 225
def test_get_agenda_pricing():
227
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
226
    agenda = Agenda.objects.create(label='Foo bar')
228 227
    pricing = Pricing.objects.create(label='Foo bar')
229
    event = Event.objects.create(
230
        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
231
    )
228
    event = {
229
        'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat(),
230
    }
232 231

  
233 232
    # not found
234 233
    with pytest.raises(AgendaPricingNotFound):
235
        AgendaPricing.get_agenda_pricing(event)
234
        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
236 235

  
237 236
    # ok
238 237
    agenda_pricing = AgendaPricing.objects.create(
......
241 240
        date_start=datetime.date(year=2021, month=9, day=1),
242 241
        date_end=datetime.date(year=2021, month=10, day=1),
243 242
    )
244
    assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing
243
    assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
245 244

  
246 245
    # more than one matching
247 246
    AgendaPricing.objects.create(
......
251 250
        date_end=datetime.date(year=2021, month=9, day=16),
252 251
    )
253 252
    with pytest.raises(AgendaPricingNotFound):
254
        AgendaPricing.get_agenda_pricing(event)
253
        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
255 254

  
256 255

  
257
@pytest.mark.xfail(reason='no link to events yet')
258 256
@pytest.mark.parametrize(
259 257
    'event_date, found',
260 258
    [
......
269 267
    ],
270 268
)
271 269
def test_get_agenda_pricing_event_date(event_date, found):
272
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
270
    agenda = Agenda.objects.create(label='Foo bar')
273 271
    pricing = Pricing.objects.create(label='Foo bar')
274
    event = Event.objects.create(
275
        agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10
276
    )
272
    event = {
273
        'start_datetime': make_aware(datetime.datetime(*event_date)).isoformat(),
274
    }
277 275
    agenda_pricing = AgendaPricing.objects.create(
278 276
        agenda=agenda,
279 277
        pricing=pricing,
......
281 279
        date_end=datetime.date(year=2021, month=10, day=1),
282 280
    )
283 281
    if found:
284
        assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing
282
        assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
285 283
    else:
286 284
        with pytest.raises(AgendaPricingNotFound):
287
            AgendaPricing.get_agenda_pricing(event)
285
            AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
288 286

  
289 287

  
290
@pytest.mark.xfail(reason='no link to events yet')
291 288
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
292 289
def test_get_pricing_context(mock_send, context, nocache):
293
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
294
    event = Event.objects.create(
295
        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
296
    )
290
    agenda = Agenda.objects.create(label='Foo bar')
297 291
    pricing = Pricing.objects.create(label='Foo bar')
298 292
    agenda_pricing = AgendaPricing.objects.create(
299 293
        agenda=agenda,
......
301 295
        date_start=datetime.date(year=2021, month=9, day=1),
302 296
        date_end=datetime.date(year=2021, month=10, day=1),
303 297
    )
304
    assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {}
298
    assert (
299
        agenda_pricing.get_pricing_context(
300
            request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
301
        )
302
        == {}
303
    )
305 304
    pricing.extra_variables = {
306 305
        'foo': 'bar',
307 306
        'qf': '{{ 40|add:2 }}',
308 307
        'domicile': 'commune',
309 308
        'ids': '{{ cards|objects:"foo"|getlist:"id"|join:"," }}',
310
        'bad': '{% if foo %}',
309
        'syntax_error': '{% for %}',
310
        'variable_error': '{{ "foo"|add:user.email }}',
311
        'event': '{{ data.event.foo }}',
312
        'subscription': '{{ data.subscription.foo }}',
313
        'booking': '{{ data.booking.foo }}',
311 314
    }
312 315
    pricing.save()
313
    assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {
316
    data = {
317
        'event': {'foo': 42},
318
        'subscription': {'foo': 43},
319
        'booking': {'foo': 44},
320
    }
321
    assert agenda_pricing.get_pricing_context(
322
        request=context['request'], data=data, user_external_id='child:42', adult_external_id='parent:35'
323
    ) == {
314 324
        'foo': 'bar',
315 325
        'qf': '42',
316 326
        'domicile': 'commune',
317 327
        'ids': '1,2',
328
        'event': '42',
329
        'subscription': '43',
330
        'booking': '44',
318 331
    }
319 332

  
320 333
    # user_external_id and adult_external_id can be used in variables
......
323 336
    }
324 337
    pricing.save()
325 338
    mock_send.reset_mock()
326
    agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
339
    agenda_pricing.get_pricing_context(
340
        request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
341
    )
327 342
    assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
328 343
    assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url
329 344
    pricing.extra_variables = {
......
331 346
    }
332 347
    pricing.save()
333 348
    mock_send.reset_mock()
334
    agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
349
    agenda_pricing.get_pricing_context(
350
        request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
351
    )
335 352
    assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
336 353
    assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
337 354

  
......
475 492
    )
476 493

  
477 494

  
478
@pytest.mark.xfail(reason='no link to events yet')
479
def test_get_booking_modifier_event_not_checked():
495
def test_get_booking_modifier_unknown_check_status():
480 496
    agenda = Agenda.objects.create(label='Foo bar')
481
    event = Event.objects.create(
482
        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
483
    )
484
    pricing = Pricing.objects.create(label='Foo bar')
485
    agenda_pricing = AgendaPricing.objects.create(
486
        agenda=agenda,
487
        pricing=pricing,
488
        date_start=datetime.date(year=2021, month=9, day=1),
489
        date_end=datetime.date(year=2021, month=10, day=1),
490
    )
491
    with pytest.raises(PricingEventNotCheckedError):
492
        agenda_pricing.get_booking_modifier(event, 'child:42')
493

  
494

  
495
@pytest.mark.xfail(reason='no link to events yet')
496
def test_get_booking_modifier_no_subscription():
497
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
498
    event = Event.objects.create(
499
        agenda=agenda,
500
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
501
        places=10,
502
        checked=True,
503
    )
504
    Subscription.objects.create(
505
        agenda=agenda,
506
        user_external_id='child:35',  # another user
507
        date_start=datetime.date(year=2021, month=9, day=1),
508
        date_end=datetime.date(year=2021, month=10, day=1),
509
    )
510 497
    pricing = Pricing.objects.create(label='Foo bar')
511 498
    agenda_pricing = AgendaPricing.objects.create(
512 499
        agenda=agenda,
......
514 501
        date_start=datetime.date(year=2021, month=9, day=1),
515 502
        date_end=datetime.date(year=2021, month=10, day=1),
516 503
    )
517
    with pytest.raises(PricingSubscriptionError):
518
        agenda_pricing.get_booking_modifier(event, 'child:42')
519

  
520
    # more than one subscription found !
521
    Subscription.objects.create(
522
        agenda=agenda,
523
        user_external_id='child:42',
524
        date_start=datetime.date(year=2021, month=9, day=1),
525
        date_end=datetime.date(year=2021, month=10, day=1),
526
    )
527
    Subscription.objects.create(
528
        agenda=agenda,
529
        user_external_id='child:42',
530
        date_start=datetime.date(year=2021, month=9, day=1),
531
        date_end=datetime.date(year=2021, month=10, day=1),
532
    )
533
    with pytest.raises(PricingSubscriptionError):
534
        agenda_pricing.get_booking_modifier(event, 'child:42')
504
    check_status = {'status': 'unknown'}
505
    with pytest.raises(PricingUnknownCheckStatusError):
506
        agenda_pricing.get_booking_modifier(check_status=check_status)
535 507

  
536 508

  
537
@pytest.mark.parametrize(
538
    'event_date, success',
539
    [
540
        # just before first day
541
        ((2021, 8, 31, 12, 00), False),
542
        # first day
543
        ((2021, 9, 1, 12, 00), True),
544
        # last day
545
        ((2021, 9, 30, 12, 00), True),
546
        # just after last day
547
        ((2021, 10, 1, 12, 00), False),
548
    ],
549
)
550
@pytest.mark.xfail(reason='no link to events yet')
551
def test_get_booking_modifier_subscription_date(event_date, success):
552
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
553
    Subscription.objects.create(
554
        agenda=agenda,
555
        user_external_id='child:42',
556
        date_start=datetime.date(year=2021, month=9, day=1),
557
        date_end=datetime.date(year=2021, month=10, day=1),
558
    )
559
    event = Event.objects.create(
560
        agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10, checked=True
561
    )
509
def test_get_booking_modifier_event_not_checked():
510
    agenda = Agenda.objects.create(label='Foo bar')
562 511
    pricing = Pricing.objects.create(label='Foo bar')
563 512
    agenda_pricing = AgendaPricing.objects.create(
564 513
        agenda=agenda,
......
566 515
        date_start=datetime.date(year=2021, month=9, day=1),
567 516
        date_end=datetime.date(year=2021, month=10, day=1),
568 517
    )
569
    if success:
570
        assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
571
            'status': 'not-booked',
572
            'modifier_type': 'rate',
573
            'modifier_rate': 0,
574
        }
575
    else:
576
        with pytest.raises(PricingSubscriptionError):
577
            agenda_pricing.get_booking_modifier(event, 'child:42')
518
    check_status = {'status': 'error', 'error_reason': 'event-not-checked'}
519
    with pytest.raises(PricingEventNotCheckedError):
520
        agenda_pricing.get_booking_modifier(check_status=check_status)
578 521

  
579 522

  
580
@pytest.mark.xfail(reason='no link to events yet')
581 523
def test_get_booking_modifier_no_booking():
582
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
583
    Subscription.objects.create(
584
        agenda=agenda,
585
        user_external_id='child:42',
586
        date_start=datetime.date(year=2021, month=9, day=1),
587
        date_end=datetime.date(year=2021, month=10, day=1),
588
    )
589
    event = Event.objects.create(
590
        agenda=agenda,
591
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
592
        places=10,
593
        checked=True,
594
    )
524
    agenda = Agenda.objects.create(label='Foo bar')
595 525
    pricing = Pricing.objects.create(label='Foo bar')
596 526
    agenda_pricing = AgendaPricing.objects.create(
597 527
        agenda=agenda,
......
599 529
        date_start=datetime.date(year=2021, month=9, day=1),
600 530
        date_end=datetime.date(year=2021, month=10, day=1),
601 531
    )
602
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
532
    check_status = {'status': 'not-booked'}
533
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
603 534
        'status': 'not-booked',
604 535
        'modifier_type': 'rate',
605 536
        'modifier_rate': 0,
606 537
    }
607 538

  
608 539
    # more than one booking found !
609
    Booking.objects.create(event=event, user_external_id='child:42')
610
    Booking.objects.create(event=event, user_external_id='child:42')
540
    check_status = {'status': 'error', 'error_reason': 'too-many-bookings-found'}
611 541
    with pytest.raises(PricingMultipleBookingError):
612
        agenda_pricing.get_booking_modifier(event, 'child:42')
542
        agenda_pricing.get_booking_modifier(check_status=check_status)
613 543

  
614 544

  
615
@pytest.mark.xfail(reason='no link to events yet')
616 545
def test_get_booking_modifier_booking_cancelled():
617
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
618
    Subscription.objects.create(
619
        agenda=agenda,
620
        user_external_id='child:42',
621
        date_start=datetime.date(year=2021, month=9, day=1),
622
        date_end=datetime.date(year=2021, month=10, day=1),
623
    )
624
    event = Event.objects.create(
625
        agenda=agenda,
626
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
627
        places=10,
628
        checked=True,
629
    )
630
    Booking.objects.create(event=event, user_external_id='child:42', cancellation_datetime=now())
546
    agenda = Agenda.objects.create(label='Foo bar')
631 547
    pricing = Pricing.objects.create(label='Foo bar')
632 548
    agenda_pricing = AgendaPricing.objects.create(
633 549
        agenda=agenda,
......
635 551
        date_start=datetime.date(year=2021, month=9, day=1),
636 552
        date_end=datetime.date(year=2021, month=10, day=1),
637 553
    )
638
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
554
    check_status = {'status': 'cancelled'}
555
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
639 556
        'status': 'cancelled',
640 557
        'modifier_type': 'rate',
641 558
        'modifier_rate': 0,
642 559
    }
643 560

  
644 561

  
645
@pytest.mark.xfail(reason='no link to events yet')
646 562
def test_get_booking_modifier_booking_not_checked():
647
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
648
    Subscription.objects.create(
649
        agenda=agenda,
650
        user_external_id='child:42',
651
        date_start=datetime.date(year=2021, month=9, day=1),
652
        date_end=datetime.date(year=2021, month=10, day=1),
653
    )
654
    event = Event.objects.create(
655
        agenda=agenda,
656
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
657
        places=10,
658
        checked=True,
659
    )
660
    Booking.objects.create(event=event, user_external_id='child:42')
563
    agenda = Agenda.objects.create(label='Foo bar')
661 564
    pricing = Pricing.objects.create(label='Foo bar')
662 565
    agenda_pricing = AgendaPricing.objects.create(
663 566
        agenda=agenda,
......
665 568
        date_start=datetime.date(year=2021, month=9, day=1),
666 569
        date_end=datetime.date(year=2021, month=10, day=1),
667 570
    )
571
    check_status = {'status': 'error', 'error_reason': 'booking-not-checked'}
668 572
    with pytest.raises(PricingBookingNotCheckedError):
669
        agenda_pricing.get_booking_modifier(event, 'child:42')
573
        agenda_pricing.get_booking_modifier(check_status=check_status)
670 574

  
671 575

  
672
@pytest.mark.xfail(reason='no link to events yet')
673
def test_get_booking_modifier_booking_absence():
674
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
675
    Subscription.objects.create(
576
def test_get_booking_modifier_unknown_check_type():
577
    agenda = Agenda.objects.create(label='Foo bar')
578
    pricing = Pricing.objects.create(label='Foo bar')
579
    agenda_pricing = AgendaPricing.objects.create(
676 580
        agenda=agenda,
677
        user_external_id='child:42',
581
        pricing=pricing,
678 582
        date_start=datetime.date(year=2021, month=9, day=1),
679 583
        date_end=datetime.date(year=2021, month=10, day=1),
680 584
    )
681
    event = Event.objects.create(
682
        agenda=agenda,
683
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
684
        places=10,
685
        checked=True,
686
    )
687
    booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=False)
585
    check_status = {'status': 'presence', 'check_type': 'unknown'}
586
    with pytest.raises(PricingBookingCheckTypeError) as e:
587
        agenda_pricing.get_booking_modifier(check_status=check_status)
588
    assert e.value.details == {
589
        'reason': 'not-found',
590
    }
591
    check_status = {'status': 'absence', 'check_type': 'unknown'}
592
    with pytest.raises(PricingBookingCheckTypeError) as e:
593
        agenda_pricing.get_booking_modifier(check_status=check_status)
594
    assert e.value.details == {
595
        'reason': 'not-found',
596
    }
597

  
598

  
599
def test_get_booking_modifier_booking_absence():
600
    agenda = Agenda.objects.create(label='Foo bar')
688 601
    pricing = Pricing.objects.create(label='Foo bar')
689 602
    agenda_pricing = AgendaPricing.objects.create(
690 603
        agenda=agenda,
......
694 607
    )
695 608

  
696 609
    # no check type
697
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
610
    check_status = {'status': 'absence', 'check_type': ''}
611
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
698 612
        'status': 'absence',
699 613
        'check_type_group': None,
700 614
        'check_type': None,
......
702 616
        'modifier_rate': 0,
703 617
    }
704 618

  
705
    # check_type but incomplete configuration
619
    # check_type but not configured on agenda
706 620
    group = CheckTypeGroup.objects.create(label='Foo bar')
707 621
    check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
708
    booking.user_check_type = check_type
709
    booking.save()
622
    check_status = {'status': 'absence', 'check_type': check_type.slug}
710 623
    with pytest.raises(PricingBookingCheckTypeError) as e:
711
        agenda_pricing.get_booking_modifier(event, 'child:42')
624
        agenda_pricing.get_booking_modifier(check_status=check_status)
625
    assert e.value.details == {
626
        'reason': 'not-found',
627
    }
628
    # incomplete configuration
629
    agenda.check_type_group = group
630
    agenda.save()
631
    with pytest.raises(PricingBookingCheckTypeError) as e:
632
        agenda_pricing.get_booking_modifier(check_status=check_status)
712 633
    assert e.value.details == {
713 634
        'check_type_group': 'foo-bar',
714 635
        'check_type': 'foo-reason',
......
717 638

  
718 639
    check_type.pricing = 42
719 640
    check_type.save()
720
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
641
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
721 642
        'status': 'absence',
722 643
        'check_type_group': 'foo-bar',
723 644
        'check_type': 'foo-reason',
......
727 648

  
728 649
    check_type.pricing = 0
729 650
    check_type.save()
730
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
651
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
731 652
        'status': 'absence',
732 653
        'check_type_group': 'foo-bar',
733 654
        'check_type': 'foo-reason',
......
738 659
    check_type.pricing = None
739 660
    check_type.pricing_rate = 20
740 661
    check_type.save()
741
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
662
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
742 663
        'status': 'absence',
743 664
        'check_type_group': 'foo-bar',
744 665
        'check_type': 'foo-reason',
......
748 669

  
749 670
    check_type.pricing_rate = 0
750 671
    check_type.save()
751
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
672
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
752 673
        'status': 'absence',
753 674
        'check_type_group': 'foo-bar',
754 675
        'check_type': 'foo-reason',
......
760 681
    check_type.kind = 'presence'
761 682
    check_type.save()
762 683
    with pytest.raises(PricingBookingCheckTypeError) as e:
763
        agenda_pricing.get_booking_modifier(event, 'child:42')
684
        agenda_pricing.get_booking_modifier(check_status=check_status)
764 685
    assert e.value.details == {
765 686
        'check_type_group': 'foo-bar',
766 687
        'check_type': 'foo-reason',
......
768 689
    }
769 690

  
770 691

  
771
@pytest.mark.xfail(reason='no link to events yet')
772 692
def test_get_booking_modifier_booking_presence():
773
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
774
    Subscription.objects.create(
775
        agenda=agenda,
776
        user_external_id='child:42',
777
        date_start=datetime.date(year=2021, month=9, day=1),
778
        date_end=datetime.date(year=2021, month=10, day=1),
779
    )
780
    event = Event.objects.create(
781
        agenda=agenda,
782
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
783
        places=10,
784
        checked=True,
785
    )
786
    booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=True)
693
    agenda = Agenda.objects.create(label='Foo bar')
787 694
    pricing = Pricing.objects.create(label='Foo bar')
788 695
    agenda_pricing = AgendaPricing.objects.create(
789 696
        agenda=agenda,
......
793 700
    )
794 701

  
795 702
    # no check type
796
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
703
    check_status = {'status': 'presence', 'check_type': ''}
704
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
797 705
        'status': 'presence',
798 706
        'check_type_group': None,
799 707
        'check_type': None,
......
801 709
        'modifier_rate': 100,
802 710
    }
803 711

  
804
    # check_type but incomplete configuration
712
    # check_type but not configured on agenda
805 713
    group = CheckTypeGroup.objects.create(label='Foo bar')
806 714
    check_type = CheckType.objects.create(label='Foo reason', group=group, kind='presence')
807
    booking.user_check_type = check_type
808
    booking.save()
715
    check_status = {'status': 'presence', 'check_type': check_type.slug}
716
    with pytest.raises(PricingBookingCheckTypeError) as e:
717
        agenda_pricing.get_booking_modifier(check_status=check_status)
718
    assert e.value.details == {
719
        'reason': 'not-found',
720
    }
721
    # incomplete configuration
722
    agenda.check_type_group = group
723
    agenda.save()
809 724
    with pytest.raises(PricingBookingCheckTypeError) as e:
810
        agenda_pricing.get_booking_modifier(event, 'child:42')
725
        agenda_pricing.get_booking_modifier(check_status=check_status)
811 726
    assert e.value.details == {
812 727
        'check_type_group': 'foo-bar',
813 728
        'check_type': 'foo-reason',
......
816 731

  
817 732
    check_type.pricing = 42
818 733
    check_type.save()
819
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
734
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
820 735
        'status': 'presence',
821 736
        'check_type_group': 'foo-bar',
822 737
        'check_type': 'foo-reason',
......
826 741

  
827 742
    check_type.pricing = 0
828 743
    check_type.save()
829
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
744
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
830 745
        'status': 'presence',
831 746
        'check_type_group': 'foo-bar',
832 747
        'check_type': 'foo-reason',
......
837 752
    check_type.pricing = None
838 753
    check_type.pricing_rate = 150
839 754
    check_type.save()
840
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
755
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
841 756
        'status': 'presence',
842 757
        'check_type_group': 'foo-bar',
843 758
        'check_type': 'foo-reason',
......
847 762

  
848 763
    check_type.pricing_rate = 0
849 764
    check_type.save()
850
    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
765
    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
851 766
        'status': 'presence',
852 767
        'check_type_group': 'foo-bar',
853 768
        'check_type': 'foo-reason',
......
859 774
    check_type.kind = 'absence'
860 775
    check_type.save()
861 776
    with pytest.raises(PricingBookingCheckTypeError) as e:
862
        agenda_pricing.get_booking_modifier(event, 'child:42')
777
        agenda_pricing.get_booking_modifier(check_status=check_status)
863 778
    assert e.value.details == {
864 779
        'check_type_group': 'foo-bar',
865 780
        'check_type': 'foo-reason',
......
867 782
    }
868 783

  
869 784

  
870
@pytest.mark.xfail(reason='no link to events yet')
871 785
def test_get_pricing_data(context):
872
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
873
    event = Event.objects.create(
874
        agenda=agenda,
875
        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
876
        places=10,
877
        checked=True,
878
    )
879
    Subscription.objects.create(
880
        agenda=agenda,
881
        user_external_id='child:42',
882
        date_start=datetime.date(year=2021, month=9, day=1),
883
        date_end=datetime.date(year=2021, month=10, day=1),
884
    )
786
    agenda = Agenda.objects.create(label='Foo bar')
885 787
    category = CriteriaCategory.objects.create(label='Foo', slug='foo')
886 788
    criteria = Criteria.objects.create(label='Bar', slug='bar', condition='True', category=category)
887 789
    pricing = Pricing.objects.create(
......
902 804
            'foo:bar': 42,
903 805
        },
904 806
    )
905
    assert AgendaPricing.get_pricing_data(context['request'], event, 'child:42', 'parent:35') == {
807
    assert AgendaPricing.get_pricing_data(
808
        request=context['request'],
809
        agenda=agenda,
810
        event={'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat()},
811
        subscription={},
812
        check_status={'status': 'not-booked'},
813
        booking={},
814
        user_external_id='child:42',
815
        adult_external_id='parent:35',
816
    ) == {
906 817
        'pricing': 0,
907 818
        'calculation_details': {
908 819
            'pricing': 42,
tests/settings.py
1 1
import os
2 2

  
3
TIME_ZONE = 'Europe/Paris'
3 4
LANGUAGE_CODE = 'en-us'
4
TIME_ZONE = 'UTC'
5 5

  
6 6
CACHES = {
7 7
    'default': {
......
38 38
            'secondary': True,
39 39
        },
40 40
    },
41
    'wcs': {
42
        'default': {
43
            'title': 'test',
44
            'url': 'http://example.org',
45
            'secret': 'chrono',
46
            'orig': 'chrono',
47
            'backoffice-menu-url': 'http://example.org/manage/',
48
        }
49
    },
41 50
}
42
-