From cf75e822eea928d4462b95c6b6677b0621dea738 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 2 Jun 2022 12:04:45 +0200
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

diff --git a/lingo/pricing/models.py b/lingo/pricing/models.py
index 34d4581..5468485 100644
--- a/lingo/pricing/models.py
+++ b/lingo/pricing/models.py
@@ -16,16 +16,17 @@
 
 import copy
 import dataclasses
+import datetime
 import decimal
 from typing import List
 
 from django.contrib.postgres.fields import JSONField
 from django.db import models
-from django.template import Context, RequestContext, Template, TemplateSyntaxError
+from django.template import Context, RequestContext, Template, TemplateSyntaxError, VariableDoesNotExist
 from django.utils.text import slugify
 from django.utils.translation import ugettext_lazy as _
 
-from lingo.agendas.models import Agenda
+from lingo.agendas.models import Agenda, CheckType
 from lingo.utils.misc import AgendaImportError, clean_import_data, generate_slug
 
 
@@ -51,15 +52,15 @@ class PricingDataFormatError(PricingError):
     pass
 
 
-class PricingEventNotCheckedError(PricingError):
+class PricingUnknownCheckStatusError(PricingError):
     pass
 
 
-class PricingBookingNotCheckedError(PricingError):
+class PricingEventNotCheckedError(PricingError):
     pass
 
 
-class PricingSubscriptionError(PricingError):
+class PricingBookingNotCheckedError(PricingError):
     pass
 
 
@@ -203,10 +204,9 @@ class Pricing(models.Model):
         context.push(original_context)
         for key, tplt in (self.extra_variables or {}).items():
             try:
-                template = Template(tplt)
-            except TemplateSyntaxError:
+                result[key] = Template(tplt).render(context)
+            except (TemplateSyntaxError, VariableDoesNotExist):
                 continue
-            result[key] = template.render(context)
         return result
 
     def get_extra_variables_keys(self):
@@ -353,12 +353,26 @@ class AgendaPricing(models.Model):
         }
 
     @staticmethod
-    def get_pricing_data(request, event, user_external_id, adult_external_id):
-        agenda_pricing = AgendaPricing.get_agenda_pricing(event)
-        context = agenda_pricing.get_pricing_context(request, user_external_id, adult_external_id)
-        pricing, criterias = agenda_pricing.compute_pricing(context)
-        modifier = agenda_pricing.get_booking_modifier(event, user_external_id)
-        return agenda_pricing.aggregate_pricing_data(pricing, criterias, context, modifier)
+    def get_pricing_data(
+        request, agenda, event, subscription, check_status, booking, user_external_id, adult_external_id
+    ):
+        agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
+        data = {
+            'event': event,
+            'subscription': subscription,
+            'booking': booking,
+        }
+        context = agenda_pricing.get_pricing_context(
+            request=request,
+            data=data,
+            user_external_id=user_external_id,
+            adult_external_id=adult_external_id,
+        )
+        pricing, criterias = agenda_pricing.compute_pricing(context=context)
+        modifier = agenda_pricing.get_booking_modifier(check_status=check_status)
+        return agenda_pricing.aggregate_pricing_data(
+            pricing=pricing, criterias=criterias, context=context, modifier=modifier
+        )
 
     def aggregate_pricing_data(self, pricing, criterias, context, modifier):
         if modifier['modifier_type'] == 'fixed':
@@ -376,29 +390,18 @@ class AgendaPricing(models.Model):
         }
 
     @staticmethod
-    def get_agenda_pricing(event):
-        agenda = event.agenda
+    def get_agenda_pricing(agenda, event):
+        start_datetime = datetime.datetime.fromisoformat(event['start_datetime'])
         try:
             return agenda.agendapricing_set.get(
-                date_start__lte=event.start_datetime,
-                date_end__gt=event.start_datetime,
+                date_start__lte=start_datetime,
+                date_end__gt=start_datetime,
             )
         except (AgendaPricing.DoesNotExist, AgendaPricing.MultipleObjectsReturned):
             raise AgendaPricingNotFound
 
-    def get_subscription(self, event, user_external_id):
-        try:
-            return self.agenda.subscriptions.get(
-                user_external_id=user_external_id,
-                date_start__lte=event.start_datetime,
-                date_end__gt=event.start_datetime,
-            )
-        # noqa pylint: disable=undefined-variable
-        except (Subscription.DoesNotExist, Subscription.MultipleObjectsReturned):
-            raise PricingSubscriptionError
-
-    def get_pricing_context(self, request, user_external_id, adult_external_id):
-        context = {'user_external_id': user_external_id, 'adult_external_id': adult_external_id}
+    def get_pricing_context(self, request, data, user_external_id, adult_external_id):
+        context = {'data': data, 'user_external_id': user_external_id, 'adult_external_id': adult_external_id}
         if ':' in user_external_id:
             context['user_external_raw_id'] = user_external_id.split(':')[1]
         if ':' in adult_external_id:
@@ -442,54 +445,61 @@ class AgendaPricing(models.Model):
 
         return pricing, criterias
 
-    def get_booking_modifier(self, event, user_external_id):
-        # event must be checked
-        if event.checked is False:
-            raise PricingEventNotCheckedError
-
-        # search for an available subscription
-        self.get_subscription(event, user_external_id)
-
-        # search for a booking
-        try:
-            booking = event.booking_set.get(user_external_id=user_external_id)
-        except Booking.DoesNotExist:  # noqa pylint: disable=undefined-variable
-            # no booking
+    def get_booking_modifier(self, check_status):
+        status = check_status['status']
+        if status not in ['error', 'not-booked', 'cancelled', 'presence', 'absence']:
+            raise PricingUnknownCheckStatusError
+
+        if status == 'error':
+            reason = check_status['error_reason']
+            # event must be checked
+            if reason == 'event-not-checked':
+                raise PricingEventNotCheckedError
+            # booking must be checked
+            if reason == 'booking-not-checked':
+                raise PricingBookingNotCheckedError
+            # too many bookings found
+            if reason == 'too-many-bookings-found':
+                raise PricingMultipleBookingError
+
+        # no booking found
+        if status == 'not-booked':
             return {
                 'status': 'not-booked',
                 'modifier_type': 'rate',
                 'modifier_rate': 0,
             }
-        except Booking.MultipleObjectsReturned:  # noqa pylint: disable=undefined-variable
-            raise PricingMultipleBookingError
 
         # booking cancelled
-        if booking.cancellation_datetime is not None:
+        if status == 'cancelled':
             return {
                 'status': 'cancelled',
                 'modifier_type': 'rate',
                 'modifier_rate': 0,
             }
 
-        # booking not cancelled, is must be checked
-        if booking.user_was_present is None:
-            raise PricingBookingNotCheckedError
-
-        status = 'presence' if booking.user_was_present else 'absence'
         # no check_type, default rates
-        if booking.user_check_type is None:
+        if not check_status['check_type']:
             return {
                 'status': status,
                 'check_type_group': None,
                 'check_type': None,
                 'modifier_type': 'rate',
-                'modifier_rate': 100 if booking.user_was_present else 0,
+                'modifier_rate': 100 if status == 'presence' else 0,
             }
 
-        check_type = booking.user_check_type
-        kind_mapping = {'presence': True, 'absence': False}
+        try:
+            check_type = CheckType.objects.get(
+                group=self.agenda.check_type_group_id, slug=check_status['check_type']
+            )
+        except CheckType.DoesNotExist:
+            raise PricingBookingCheckTypeError(
+                details={
+                    'reason': 'not-found',
+                }
+            )
         # check_type kind and user_was_present mismatch
-        if kind_mapping[check_type.kind] != booking.user_was_present:
+        if check_type.kind != status:
             raise PricingBookingCheckTypeError(
                 details={
                     'check_type_group': check_type.group.slug,
diff --git a/lingo/settings.py b/lingo/settings.py
index 2b56862..8a1209f 100644
--- a/lingo/settings.py
+++ b/lingo/settings.py
@@ -89,9 +89,11 @@ TEMPLATES = [
                 'django.template.context_processors.static',
                 'django.template.context_processors.tz',
                 'django.contrib.messages.context_processors.messages',
+                'publik_django_templatetags.wcs.context_processors.cards',
             ],
             'builtins': [
                 'publik_django_templatetags.publik.templatetags.publik',
+                'publik_django_templatetags.wcs.templatetags.wcs',
                 'django.contrib.humanize.templatetags.humanize',
             ],
         },
diff --git a/tests/conftest.py b/tests/conftest.py
index c287eea..7f857c4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -26,3 +26,12 @@ def simple_user():
 @pytest.fixture
 def admin_user():
     return User.objects.create_superuser('admin', email=None, password='admin')
+
+
+@pytest.fixture
+def nocache(settings):
+    settings.CACHES = {
+        'default': {
+            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
+        }
+    }
diff --git a/tests/pricing/conftest.py b/tests/pricing/conftest.py
deleted file mode 100644
index 2f25b08..0000000
--- a/tests/pricing/conftest.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import pytest
-from django.contrib.auth.models import Group, User
-
-
-@pytest.fixture
-def simple_user():
-    try:
-        user = User.objects.get(username='user')
-    except User.DoesNotExist:
-        user = User.objects.create_user('user', password='user')
-    return user
-
-
-@pytest.fixture
-def managers_group():
-    group, _ = Group.objects.get_or_create(name='Managers')
-    return group
-
-
-@pytest.fixture
-def manager_user(managers_group):
-    try:
-        user = User.objects.get(username='manager')
-    except User.DoesNotExist:
-        user = User.objects.create_user('manager', password='manager')
-    user.groups.set([managers_group])
-    return user
-
-
-@pytest.fixture
-def admin_user():
-    try:
-        user = User.objects.get(username='admin')
-    except User.DoesNotExist:
-        user = User.objects.create_superuser('admin', email=None, password='admin')
-    return user
diff --git a/tests/pricing/test_models.py b/tests/pricing/test_models.py
index 40334ef..30729a6 100644
--- a/tests/pricing/test_models.py
+++ b/tests/pricing/test_models.py
@@ -5,7 +5,7 @@ from unittest import mock
 import pytest
 from django.template import Context
 from django.test.client import RequestFactory
-from django.utils.timezone import make_aware, now
+from django.utils.timezone import make_aware
 from publik_django_templatetags.wcs.context_processors import Cards
 
 from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
@@ -26,7 +26,7 @@ from lingo.pricing.models import (
     PricingMatrixCell,
     PricingMatrixRow,
     PricingMultipleBookingError,
-    PricingSubscriptionError,
+    PricingUnknownCheckStatusError,
 )
 
 pytestmark = pytest.mark.django_db
@@ -222,17 +222,16 @@ def test_pricing_duplicate():
     assert new_pricing.slug == 'bar'
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_agenda_pricing():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
-    event = Event.objects.create(
-        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
-    )
+    event = {
+        'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat(),
+    }
 
     # not found
     with pytest.raises(AgendaPricingNotFound):
-        AgendaPricing.get_agenda_pricing(event)
+        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
 
     # ok
     agenda_pricing = AgendaPricing.objects.create(
@@ -241,7 +240,7 @@ def test_get_agenda_pricing():
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing
+    assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
 
     # more than one matching
     AgendaPricing.objects.create(
@@ -251,10 +250,9 @@ def test_get_agenda_pricing():
         date_end=datetime.date(year=2021, month=9, day=16),
     )
     with pytest.raises(AgendaPricingNotFound):
-        AgendaPricing.get_agenda_pricing(event)
+        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 @pytest.mark.parametrize(
     'event_date, found',
     [
@@ -269,11 +267,11 @@ def test_get_agenda_pricing():
     ],
 )
 def test_get_agenda_pricing_event_date(event_date, found):
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
-    event = Event.objects.create(
-        agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10
-    )
+    event = {
+        'start_datetime': make_aware(datetime.datetime(*event_date)).isoformat(),
+    }
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
         pricing=pricing,
@@ -281,19 +279,15 @@ def test_get_agenda_pricing_event_date(event_date, found):
         date_end=datetime.date(year=2021, month=10, day=1),
     )
     if found:
-        assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing
+        assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
     else:
         with pytest.raises(AgendaPricingNotFound):
-            AgendaPricing.get_agenda_pricing(event)
+            AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 @mock.patch('requests.Session.send', side_effect=mocked_requests_send)
 def test_get_pricing_context(mock_send, context, nocache):
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    event = Event.objects.create(
-        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
-    )
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -301,20 +295,39 @@ def test_get_pricing_context(mock_send, context, nocache):
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {}
+    assert (
+        agenda_pricing.get_pricing_context(
+            request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
+        )
+        == {}
+    )
     pricing.extra_variables = {
         'foo': 'bar',
         'qf': '{{ 40|add:2 }}',
         'domicile': 'commune',
         'ids': '{{ cards|objects:"foo"|getlist:"id"|join:"," }}',
-        'bad': '{% if foo %}',
+        'syntax_error': '{% for %}',
+        'variable_error': '{{ "foo"|add:user.email }}',
+        'event': '{{ data.event.foo }}',
+        'subscription': '{{ data.subscription.foo }}',
+        'booking': '{{ data.booking.foo }}',
     }
     pricing.save()
-    assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {
+    data = {
+        'event': {'foo': 42},
+        'subscription': {'foo': 43},
+        'booking': {'foo': 44},
+    }
+    assert agenda_pricing.get_pricing_context(
+        request=context['request'], data=data, user_external_id='child:42', adult_external_id='parent:35'
+    ) == {
         'foo': 'bar',
         'qf': '42',
         'domicile': 'commune',
         'ids': '1,2',
+        'event': '42',
+        'subscription': '43',
+        'booking': '44',
     }
 
     # user_external_id and adult_external_id can be used in variables
@@ -323,7 +336,9 @@ def test_get_pricing_context(mock_send, context, nocache):
     }
     pricing.save()
     mock_send.reset_mock()
-    agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
+    agenda_pricing.get_pricing_context(
+        request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
+    )
     assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
     assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url
     pricing.extra_variables = {
@@ -331,7 +346,9 @@ def test_get_pricing_context(mock_send, context, nocache):
     }
     pricing.save()
     mock_send.reset_mock()
-    agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
+    agenda_pricing.get_pricing_context(
+        request=context['request'], data={}, user_external_id='child:42', adult_external_id='parent:35'
+    )
     assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
     assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
 
@@ -475,38 +492,8 @@ def test_compute_pricing():
     )
 
 
-@pytest.mark.xfail(reason='no link to events yet')
-def test_get_booking_modifier_event_not_checked():
+def test_get_booking_modifier_unknown_check_status():
     agenda = Agenda.objects.create(label='Foo bar')
-    event = Event.objects.create(
-        agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
-    )
-    pricing = Pricing.objects.create(label='Foo bar')
-    agenda_pricing = AgendaPricing.objects.create(
-        agenda=agenda,
-        pricing=pricing,
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    with pytest.raises(PricingEventNotCheckedError):
-        agenda_pricing.get_booking_modifier(event, 'child:42')
-
-
-@pytest.mark.xfail(reason='no link to events yet')
-def test_get_booking_modifier_no_subscription():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:35',  # another user
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -514,51 +501,13 @@ def test_get_booking_modifier_no_subscription():
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    with pytest.raises(PricingSubscriptionError):
-        agenda_pricing.get_booking_modifier(event, 'child:42')
-
-    # more than one subscription found !
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    with pytest.raises(PricingSubscriptionError):
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+    check_status = {'status': 'unknown'}
+    with pytest.raises(PricingUnknownCheckStatusError):
+        agenda_pricing.get_booking_modifier(check_status=check_status)
 
 
-@pytest.mark.parametrize(
-    'event_date, success',
-    [
-        # just before first day
-        ((2021, 8, 31, 12, 00), False),
-        # first day
-        ((2021, 9, 1, 12, 00), True),
-        # last day
-        ((2021, 9, 30, 12, 00), True),
-        # just after last day
-        ((2021, 10, 1, 12, 00), False),
-    ],
-)
-@pytest.mark.xfail(reason='no link to events yet')
-def test_get_booking_modifier_subscription_date(event_date, success):
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    event = Event.objects.create(
-        agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10, checked=True
-    )
+def test_get_booking_modifier_event_not_checked():
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -566,32 +515,13 @@ def test_get_booking_modifier_subscription_date(event_date, success):
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    if success:
-        assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
-            'status': 'not-booked',
-            'modifier_type': 'rate',
-            'modifier_rate': 0,
-        }
-    else:
-        with pytest.raises(PricingSubscriptionError):
-            agenda_pricing.get_booking_modifier(event, 'child:42')
+    check_status = {'status': 'error', 'error_reason': 'event-not-checked'}
+    with pytest.raises(PricingEventNotCheckedError):
+        agenda_pricing.get_booking_modifier(check_status=check_status)
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_booking_modifier_no_booking():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -599,35 +529,21 @@ def test_get_booking_modifier_no_booking():
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    check_status = {'status': 'not-booked'}
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'not-booked',
         'modifier_type': 'rate',
         'modifier_rate': 0,
     }
 
     # more than one booking found !
-    Booking.objects.create(event=event, user_external_id='child:42')
-    Booking.objects.create(event=event, user_external_id='child:42')
+    check_status = {'status': 'error', 'error_reason': 'too-many-bookings-found'}
     with pytest.raises(PricingMultipleBookingError):
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_booking_modifier_booking_cancelled():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    Booking.objects.create(event=event, user_external_id='child:42', cancellation_datetime=now())
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -635,29 +551,16 @@ def test_get_booking_modifier_booking_cancelled():
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    check_status = {'status': 'cancelled'}
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'cancelled',
         'modifier_type': 'rate',
         'modifier_rate': 0,
     }
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_booking_modifier_booking_not_checked():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    Booking.objects.create(event=event, user_external_id='child:42')
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -665,26 +568,36 @@ def test_get_booking_modifier_booking_not_checked():
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
+    check_status = {'status': 'error', 'error_reason': 'booking-not-checked'}
     with pytest.raises(PricingBookingNotCheckedError):
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
 
 
-@pytest.mark.xfail(reason='no link to events yet')
-def test_get_booking_modifier_booking_absence():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
+def test_get_booking_modifier_unknown_check_type():
+    agenda = Agenda.objects.create(label='Foo bar')
+    pricing = Pricing.objects.create(label='Foo bar')
+    agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
-        user_external_id='child:42',
+        pricing=pricing,
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
     )
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=False)
+    check_status = {'status': 'presence', 'check_type': 'unknown'}
+    with pytest.raises(PricingBookingCheckTypeError) as e:
+        agenda_pricing.get_booking_modifier(check_status=check_status)
+    assert e.value.details == {
+        'reason': 'not-found',
+    }
+    check_status = {'status': 'absence', 'check_type': 'unknown'}
+    with pytest.raises(PricingBookingCheckTypeError) as e:
+        agenda_pricing.get_booking_modifier(check_status=check_status)
+    assert e.value.details == {
+        'reason': 'not-found',
+    }
+
+
+def test_get_booking_modifier_booking_absence():
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -694,7 +607,8 @@ def test_get_booking_modifier_booking_absence():
     )
 
     # no check type
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    check_status = {'status': 'absence', 'check_type': ''}
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'absence',
         'check_type_group': None,
         'check_type': None,
@@ -702,13 +616,20 @@ def test_get_booking_modifier_booking_absence():
         'modifier_rate': 0,
     }
 
-    # check_type but incomplete configuration
+    # check_type but not configured on agenda
     group = CheckTypeGroup.objects.create(label='Foo bar')
     check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
-    booking.user_check_type = check_type
-    booking.save()
+    check_status = {'status': 'absence', 'check_type': check_type.slug}
     with pytest.raises(PricingBookingCheckTypeError) as e:
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
+    assert e.value.details == {
+        'reason': 'not-found',
+    }
+    # incomplete configuration
+    agenda.check_type_group = group
+    agenda.save()
+    with pytest.raises(PricingBookingCheckTypeError) as e:
+        agenda_pricing.get_booking_modifier(check_status=check_status)
     assert e.value.details == {
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -717,7 +638,7 @@ def test_get_booking_modifier_booking_absence():
 
     check_type.pricing = 42
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'absence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -727,7 +648,7 @@ def test_get_booking_modifier_booking_absence():
 
     check_type.pricing = 0
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'absence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -738,7 +659,7 @@ def test_get_booking_modifier_booking_absence():
     check_type.pricing = None
     check_type.pricing_rate = 20
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'absence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -748,7 +669,7 @@ def test_get_booking_modifier_booking_absence():
 
     check_type.pricing_rate = 0
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'absence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -760,7 +681,7 @@ def test_get_booking_modifier_booking_absence():
     check_type.kind = 'presence'
     check_type.save()
     with pytest.raises(PricingBookingCheckTypeError) as e:
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
     assert e.value.details == {
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -768,22 +689,8 @@ def test_get_booking_modifier_booking_absence():
     }
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_booking_modifier_booking_presence():
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=True)
+    agenda = Agenda.objects.create(label='Foo bar')
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         agenda=agenda,
@@ -793,7 +700,8 @@ def test_get_booking_modifier_booking_presence():
     )
 
     # no check type
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    check_status = {'status': 'presence', 'check_type': ''}
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'presence',
         'check_type_group': None,
         'check_type': None,
@@ -801,13 +709,20 @@ def test_get_booking_modifier_booking_presence():
         'modifier_rate': 100,
     }
 
-    # check_type but incomplete configuration
+    # check_type but not configured on agenda
     group = CheckTypeGroup.objects.create(label='Foo bar')
     check_type = CheckType.objects.create(label='Foo reason', group=group, kind='presence')
-    booking.user_check_type = check_type
-    booking.save()
+    check_status = {'status': 'presence', 'check_type': check_type.slug}
+    with pytest.raises(PricingBookingCheckTypeError) as e:
+        agenda_pricing.get_booking_modifier(check_status=check_status)
+    assert e.value.details == {
+        'reason': 'not-found',
+    }
+    # incomplete configuration
+    agenda.check_type_group = group
+    agenda.save()
     with pytest.raises(PricingBookingCheckTypeError) as e:
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
     assert e.value.details == {
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -816,7 +731,7 @@ def test_get_booking_modifier_booking_presence():
 
     check_type.pricing = 42
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'presence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -826,7 +741,7 @@ def test_get_booking_modifier_booking_presence():
 
     check_type.pricing = 0
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'presence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -837,7 +752,7 @@ def test_get_booking_modifier_booking_presence():
     check_type.pricing = None
     check_type.pricing_rate = 150
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'presence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -847,7 +762,7 @@ def test_get_booking_modifier_booking_presence():
 
     check_type.pricing_rate = 0
     check_type.save()
-    assert agenda_pricing.get_booking_modifier(event, 'child:42') == {
+    assert agenda_pricing.get_booking_modifier(check_status=check_status) == {
         'status': 'presence',
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -859,7 +774,7 @@ def test_get_booking_modifier_booking_presence():
     check_type.kind = 'absence'
     check_type.save()
     with pytest.raises(PricingBookingCheckTypeError) as e:
-        agenda_pricing.get_booking_modifier(event, 'child:42')
+        agenda_pricing.get_booking_modifier(check_status=check_status)
     assert e.value.details == {
         'check_type_group': 'foo-bar',
         'check_type': 'foo-reason',
@@ -867,21 +782,8 @@ def test_get_booking_modifier_booking_presence():
     }
 
 
-@pytest.mark.xfail(reason='no link to events yet')
 def test_get_pricing_data(context):
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    event = Event.objects.create(
-        agenda=agenda,
-        start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)),
-        places=10,
-        checked=True,
-    )
-    Subscription.objects.create(
-        agenda=agenda,
-        user_external_id='child:42',
-        date_start=datetime.date(year=2021, month=9, day=1),
-        date_end=datetime.date(year=2021, month=10, day=1),
-    )
+    agenda = Agenda.objects.create(label='Foo bar')
     category = CriteriaCategory.objects.create(label='Foo', slug='foo')
     criteria = Criteria.objects.create(label='Bar', slug='bar', condition='True', category=category)
     pricing = Pricing.objects.create(
@@ -902,7 +804,16 @@ def test_get_pricing_data(context):
             'foo:bar': 42,
         },
     )
-    assert AgendaPricing.get_pricing_data(context['request'], event, 'child:42', 'parent:35') == {
+    assert AgendaPricing.get_pricing_data(
+        request=context['request'],
+        agenda=agenda,
+        event={'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat()},
+        subscription={},
+        check_status={'status': 'not-booked'},
+        booking={},
+        user_external_id='child:42',
+        adult_external_id='parent:35',
+    ) == {
         'pricing': 0,
         'calculation_details': {
             'pricing': 42,
diff --git a/tests/settings.py b/tests/settings.py
index 541ee25..aec81af 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -1,7 +1,7 @@
 import os
 
+TIME_ZONE = 'Europe/Paris'
 LANGUAGE_CODE = 'en-us'
-TIME_ZONE = 'UTC'
 
 CACHES = {
     'default': {
@@ -38,4 +38,13 @@ KNOWN_SERVICES = {
             'secondary': True,
         },
     },
+    'wcs': {
+        'default': {
+            'title': 'test',
+            'url': 'http://example.org',
+            'secret': 'chrono',
+            'orig': 'chrono',
+            'backoffice-menu-url': 'http://example.org/manage/',
+        }
+    },
 }
-- 
2.35.1

