From e1fe72adf338870659671ff285bb5a4824ceabda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 29 Jul 2022 18:18:05 +0200
Subject: [PATCH 14/14] api: endpoint to get pricing data for flat fee schedule
 mode (#67675)

---
 lingo/api/serializers.py  | 216 +++++++++++++---
 tests/api/test_pricing.py | 504 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 673 insertions(+), 47 deletions(-)

diff --git a/lingo/api/serializers.py b/lingo/api/serializers.py
index 1a7e177..1f4eaa5 100644
--- a/lingo/api/serializers.py
+++ b/lingo/api/serializers.py
@@ -36,19 +36,29 @@ class CommaSeparatedStringField(serializers.ListField):
 
 class PricingComputeSerializer(serializers.Serializer):
     slots = CommaSeparatedStringField(
-        required=True, child=serializers.CharField(max_length=160, allow_blank=False)
+        required=False, child=serializers.CharField(max_length=160, allow_blank=False)
     )
+    agenda = serializers.SlugField(required=False, allow_blank=False, max_length=160)
+    agenda_pricing = serializers.SlugField(required=False, allow_blank=False, max_length=160)
+    start_date = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d'])
     user_external_id = serializers.CharField(required=True, max_length=250)
     adult_external_id = serializers.CharField(required=True, max_length=250)
 
-    agenda_slugs = []
-    agendas = {}
-    serialized_events = {}
-    event_subscriptions = {}
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self._agenda_slugs = []
+        self._agendas = {}
+        self._serialized_events = {}
+        self._event_subscriptions = {}
+        self._agenda = None
+        self._agenda_subscription = None
+        self._agenda_pricing = None
+        self._billing_date = None
 
     def validate_slots(self, value):
-        self.agendas = {a.slug: a for a in Agenda.objects.all()}
-        allowed_agenda_slugs = self.agendas.keys()
+        self._agendas = {a.slug: a for a in Agenda.objects.all()}
+        allowed_agenda_slugs = self._agendas.keys()
         agenda_slugs = set()
         for slot in value:
             try:
@@ -64,7 +74,7 @@ class PricingComputeSerializer(serializers.Serializer):
         if extra_agendas:
             extra_agendas = ', '.join(sorted(extra_agendas))
             raise ValidationError(_('Unknown agendas: %s') % extra_agendas)
-        self.agenda_slugs = sorted(agenda_slugs)
+        self._agenda_slugs = sorted(agenda_slugs)
 
         try:
             serialized_events = get_events(value)
@@ -73,61 +83,168 @@ class PricingComputeSerializer(serializers.Serializer):
         else:
             for serialized_event in serialized_events:
                 event_slug = '%s@%s' % (serialized_event['agenda'], serialized_event['slug'])
-                self.serialized_events[event_slug] = serialized_event
+                self._serialized_events[event_slug] = serialized_event
 
         return value
 
-    def get_subscriptions(self, user_external_id):
+    def _validate_agenda(self, value, start_date):
+        try:
+            self._agenda = Agenda.objects.get(slug=value)
+            try:
+                self._agenda_pricing = AgendaPricing.get_agenda_pricing(
+                    agenda=self._agenda,
+                    start_date=start_date.date(),
+                    flat_fee_schedule=True,
+                )
+                if not self._agenda_pricing.subscription_required:
+                    self._agenda_pricing = None
+            except AgendaPricingNotFound:
+                self._agenda_pricing = None
+        except Agenda.DoesNotExist:
+            raise ValidationError({'agenda': _('Unknown agenda: %s') % value})
+        return self._agenda
+
+    def _validate_agenda_pricing(self, value, start_date):
+        try:
+            self._agenda_pricing = AgendaPricing.objects.get(
+                slug=value,
+                flat_fee_schedule=True,
+                subscription_required=False,
+                date_start__lte=start_date.date(),
+                date_end__gt=start_date.date(),
+            )
+        except AgendaPricing.DoesNotExist:
+            raise ValidationError({'agenda_pricing': _('Unknown pricing: %s') % value})
+        return self._agenda_pricing
+
+    def validate(self, attrs):
+        super().validate(attrs)
+        if 'slots' not in attrs and 'agenda' not in attrs and 'agenda_pricing' not in attrs:
+            raise ValidationError(_('Either "slots", "agenda" or "agenda_pricing" parameter is required.'))
+        if 'agenda' in attrs:
+            # flat_fee_schedule mode + subscription_required True
+            if 'start_date' not in attrs:
+                raise ValidationError(
+                    {'start_date': _('This field is required when using "agenda" parameter.')}
+                )
+            self._validate_agenda(attrs['agenda'], attrs['start_date'])
+        if 'agenda_pricing' in attrs:
+            # flat_fee_schedule mode + subscription_required False
+            if 'start_date' not in attrs:
+                raise ValidationError(
+                    {'start_date': _('This field is required when using "agenda_pricing" parameter.')}
+                )
+            self._validate_agenda_pricing(attrs['agenda_pricing'], attrs['start_date'])
+        if attrs.get('start_date'):
+            # flat_fee_schedule mode: get billing_date from start_date param
+            self.get_billing_date(attrs['start_date'])
+        if attrs.get('user_external_id') and not attrs.get('agenda_pricing'):
+            # don't check subscriptions if agenda_pricing in params (== subscription_required is False)
+            self.get_subscriptions(attrs['user_external_id'], attrs.get('start_date'))
+        return attrs
+
+    def get_billing_date(self, start_date):
+        if self._agenda_pricing:
+            self._billing_date = (
+                self._agenda_pricing.billingdates.filter(date_start__lte=start_date)
+                .order_by('date_start')
+                .last()
+            )
+            if not self._billing_date:
+                self._billing_date = self._agenda_pricing.billingdates.order_by('date_start').first()
+
+    def get_subscriptions(self, user_external_id, start_date):
+        if self._serialized_events:
+            # event mode
+            self.get_subscriptions_for_events(user_external_id)
+        elif self._agenda and self._agenda_pricing:
+            # flat_fee_schedule mode
+            self.get_subscriptions_for_agenda(user_external_id, start_date)
+
+    def _get_subscription(self, subscriptions, start_date, end_date):
+        # get subscription matching start_date & end_date
+        for subscription in subscriptions:
+            sub_start_date = datetime.date.fromisoformat(subscription['date_start'])
+            sub_end_date = datetime.date.fromisoformat(subscription['date_end'])
+            if sub_start_date >= end_date:
+                continue
+            if sub_end_date <= start_date:
+                continue
+            return subscription
+
+    def get_subscriptions_for_events(self, user_external_id):
         agenda_subscriptions = {}
-        for agenda_slug in self.agenda_slugs:
+        # get subscription list for each agenda
+        for agenda_slug in self._agenda_slugs:
             try:
                 agenda_subscriptions[agenda_slug] = get_subscriptions(agenda_slug, user_external_id)
             except ChronoError as e:
                 raise ValidationError({'user_external_id': e})
 
-        self.event_subscriptions = {}
-        for event_slug, serialized_event in self.serialized_events.items():
+        # for each event, get a matching subscription
+        for event_slug, serialized_event in self._serialized_events.items():
             start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
             end_date = start_date + datetime.timedelta(days=1)
             agenda_slug = serialized_event['agenda']
-            event_subscription = None
-            for subscription in agenda_subscriptions[agenda_slug]:
-                sub_start_date = datetime.date.fromisoformat(subscription['date_start'])
-                sub_end_date = datetime.date.fromisoformat(subscription['date_end'])
-                if sub_start_date >= end_date:
-                    continue
-                if sub_end_date <= start_date:
-                    continue
-                event_subscription = subscription
-                break
+            event_subscription = self._get_subscription(
+                agenda_subscriptions[agenda_slug], start_date, end_date
+            )
             if event_subscription is None:
                 raise ValidationError(
                     {'user_external_id': _('No subscription found for event %s') % event_slug}
                 )
-            self.event_subscriptions[event_slug] = event_subscription
+            self._event_subscriptions[event_slug] = event_subscription
 
-    def validate(self, attrs):
-        super().validate(attrs)
-        if attrs.get('user_external_id') and self.serialized_events:
-            self.get_subscriptions(attrs['user_external_id'])
-        return attrs
+    def get_subscriptions_for_agenda(self, user_external_id, start_date):
+        # get subscription list for this agenda
+        try:
+            subscriptions = get_subscriptions(self._agenda.slug, user_external_id)
+        except ChronoError as e:
+            raise ValidationError({'user_external_id': e})
+
+        # get correct period, from billing_date or agenda_pricing dates
+        if not self._agenda_pricing.billingdates.exists():
+            start_date = self._agenda_pricing.date_start
+            end_date = self._agenda_pricing.date_end
+        else:
+            start_date = self._billing_date.date_start
+            next_billing_date = self._agenda_pricing.billingdates.filter(date_start__gt=start_date).first()
+            end_date = next_billing_date.date_start if next_billing_date else self._agenda_pricing.date_end
+
+        # get a matching subscriptions
+        self._agenda_subscription = self._get_subscription(subscriptions, start_date, end_date)
+        if not self._agenda_subscription:
+            raise ValidationError(
+                {'user_external_id': _('No subscription found for agenda %s') % self._agenda.slug}
+            )
 
     def compute(self, request):
-        if not self.serialized_events or not self.event_subscriptions:
-            return
+        try:
+            if not self.validated_data.get('slots'):
+                return self.compute_for_flat_fee_schedule(request)
+            return self.compute_for_event(request)
+        except PricingError as e:
+            return {
+                'error': type(e),
+                'error_details': e.details,
+            }
+
+    def compute_for_event(self, request):
         result = []
-        event_slugs = sorted(self.serialized_events.keys())
+        event_slugs = sorted(self._serialized_events.keys())
         for event_slug in event_slugs:
-            serialized_event = self.serialized_events[event_slug]
+            serialized_event = self._serialized_events[event_slug]
             start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
-            agenda = self.agendas[serialized_event['agenda']]
+            agenda = self._agendas[serialized_event['agenda']]
             try:
-                agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
+                agenda_pricing = AgendaPricing.get_agenda_pricing(
+                    agenda=agenda, start_date=start_date, flat_fee_schedule=False
+                )
                 pricing_data = agenda_pricing.get_pricing_data_for_event(
                     request=request,
                     agenda=agenda,
                     event=serialized_event,
-                    subscription=self.event_subscriptions[event_slug],
+                    subscription=self._event_subscriptions[event_slug],
                     check_status={
                         'status': 'presence',
                         'check_type': None,
@@ -151,3 +268,30 @@ class PricingComputeSerializer(serializers.Serializer):
 
         result = sorted(result, key=lambda d: d['event'])
         return result
+
+    def compute_for_flat_fee_schedule(self, request):
+        result = {}
+        if self._agenda:
+            result['agenda'] = self._agenda.slug
+            if not self._agenda_pricing:
+                result['error'] = _('No agenda pricing found for agenda %s') % self._agenda.slug
+                return result
+        else:
+            result['agenda_pricing'] = self._agenda_pricing.slug
+
+        try:
+            pricing_data = self._agenda_pricing.get_pricing_data(
+                request=request,
+                pricing_date=(
+                    self._billing_date.date_start if self._billing_date else self._agenda_pricing.date_start
+                ),
+                subscription=self._agenda_subscription,
+                user_external_id=self.validated_data['user_external_id'],
+                adult_external_id=self.validated_data['adult_external_id'],
+            )
+            result['pricing_data'] = pricing_data
+            return result
+        except PricingError as e:
+            result['error'] = type(e).__name__
+            result['error_details'] = e.details
+            return result
diff --git a/tests/api/test_pricing.py b/tests/api/test_pricing.py
index 3b3508a..ebe0052 100644
--- a/tests/api/test_pricing.py
+++ b/tests/api/test_pricing.py
@@ -13,7 +13,17 @@ pytestmark = pytest.mark.django_db
 def test_pricing_compute_params(app, user):
     app.authorization = ('Basic', ('john.doe', 'password'))
 
-    # missing slots
+    Agenda.objects.create(label='Foo')
+    pricing = Pricing.objects.create(label='Foo bar')
+    AgendaPricing.objects.create(
+        label='Foo',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2021, month=10, day=1),
+        flat_fee_schedule=True,
+    )
+
+    # missing slots, agenda, agenda_pricing
     resp = app.get(
         '/api/pricing/compute/',
         params={'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
@@ -21,27 +31,59 @@ def test_pricing_compute_params(app, user):
     )
     assert resp.json['err'] == 1
     assert resp.json['err_desc'] == 'invalid payload'
-    assert resp.json['errors']['slots'] == ['This field is required.']
+    assert resp.json['errors'] == {
+        'non_field_errors': ['Either "slots", "agenda" or "agenda_pricing" parameter is required.']
+    }
 
-    # missing user_external_id
+    # missing start_date
     resp = app.get(
         '/api/pricing/compute/',
-        params={'slots': 'foo@foo', 'adult_external_id': 'adult:1'},
+        params={'agenda': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
         status=400,
     )
     assert resp.json['err'] == 1
     assert resp.json['err_desc'] == 'invalid payload'
-    assert resp.json['errors']['user_external_id'] == ['This field is required.']
-
-    # missing adult_external_id
+    assert resp.json['errors']['start_date'] == ['This field is required when using "agenda" parameter.']
     resp = app.get(
         '/api/pricing/compute/',
-        params={'slots': 'foo@foo', 'user_external_id': 'user:1'},
+        params={'agenda_pricing': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
         status=400,
     )
     assert resp.json['err'] == 1
     assert resp.json['err_desc'] == 'invalid payload'
-    assert resp.json['errors']['adult_external_id'] == ['This field is required.']
+    assert resp.json['errors']['start_date'] == [
+        'This field is required when using "agenda_pricing" parameter.'
+    ]
+
+    params = [
+        {'slots': 'foo@foo'},
+        {'agenda': 'foo', 'start_date': '2021-09-01'},
+        {'agenda_pricing': 'foo', 'start_date': '2021-09-01'},
+    ]
+    for param in params:
+        # missing user_external_id
+        _param = param.copy()
+        _param.update({'adult_external_id': 'adult:1'})
+        resp = app.get(
+            '/api/pricing/compute/',
+            params=_param,
+            status=400,
+        )
+        assert resp.json['err'] == 1
+        assert resp.json['err_desc'] == 'invalid payload'
+        assert resp.json['errors']['user_external_id'] == ['This field is required.']
+
+        # missing adult_external_id
+        _param = param.copy()
+        _param.update({'user_external_id': 'user:1'})
+        resp = app.get(
+            '/api/pricing/compute/',
+            params=_param,
+            status=400,
+        )
+        assert resp.json['err'] == 1
+        assert resp.json['err_desc'] == 'invalid payload'
+        assert resp.json['errors']['adult_external_id'] == ['This field is required.']
 
 
 def test_pricing_compute_slots(app, user):
@@ -100,6 +142,74 @@ def test_pricing_compute_slots(app, user):
     assert resp.json['errors']['slots'] == ['Unknown agendas: agenda, agenda2']
 
 
+def test_pricing_compute_agenda(app, user):
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    # unknown agenda
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'agenda',
+            'start_date': '2021-09-01',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+        status=400,
+    )
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == 'invalid payload'
+    assert resp.json['errors']['agenda'] == ['Unknown agenda: agenda']
+
+
+def test_pricing_compute_agenda_pricing(app, user):
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    def test():
+        resp = app.get(
+            '/api/pricing/compute/',
+            params={
+                'agenda_pricing': 'baz',
+                'start_date': '2021-09-01',
+                'user_external_id': 'user:1',
+                'adult_external_id': 'adult:1',
+            },
+            status=400,
+        )
+        assert resp.json['err'] == 1
+        assert resp.json['err_desc'] == 'invalid payload'
+        assert resp.json['errors']['agenda_pricing'] == ['Unknown pricing: baz']
+
+    # unknown agenda_pricing
+    test()
+
+    # bad dates
+    pricing = Pricing.objects.create(label='Foo bar')
+    agenda_pricing = AgendaPricing.objects.create(
+        label='Baz',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=8, day=1),
+        date_end=datetime.date(year=2021, month=9, day=1),
+        flat_fee_schedule=True,
+        subscription_required=False,
+    )
+    test()
+    agenda_pricing.date_start = datetime.date(year=2021, month=9, day=3)
+    agenda_pricing.date_end = datetime.date(year=2021, month=10, day=1)
+    agenda_pricing.save()
+    test()
+
+    # wrong flat_fee_schedule value
+    agenda_pricing.flat_fee_schedule = False
+    agenda_pricing.save()
+    test()
+
+    # wrong subscription_required value
+    agenda_pricing.flat_fee_schedule = True
+    agenda_pricing.subscription_required = True
+    agenda_pricing.save()
+    test()
+
+
 @mock.patch('lingo.api.serializers.get_events')
 def test_pricing_compute_events_error(mock_events, app, user):
     Agenda.objects.create(label='Agenda')
@@ -147,7 +257,7 @@ def test_pricing_compute_subscriptions_error(mock_subscriptions, mock_events, ap
 @mock.patch('lingo.api.serializers.get_events')
 @mock.patch('lingo.api.serializers.get_subscriptions')
 @mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
-def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
+def test_pricing_compute_for_event(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
     agenda = Agenda.objects.create(label='Agenda')
     agenda2 = Agenda.objects.create(label='Agenda2')
     app.authorization = ('Basic', ('john.doe', 'password'))
@@ -277,12 +387,12 @@ def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_event
         },
     ]
 
-    # ok
     pricing = Pricing.objects.create(label='Foo bar')
     agenda_pricing = AgendaPricing.objects.create(
         pricing=pricing,
         date_start=datetime.date(year=2021, month=9, day=1),
         date_end=datetime.date(year=2021, month=10, day=1),
+        flat_fee_schedule=True,  # wrong config
     )
     agenda_pricing.agendas.add(agenda, agenda2)
     resp = app.get(
@@ -293,6 +403,28 @@ def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_event
             'adult_external_id': 'adult:1',
         },
     )
+    assert resp.json['data'] == [
+        {
+            'event': 'agenda2@event-baz-slug',
+            'error': 'No agenda pricing found for event agenda2@event-baz-slug',
+        },
+        {
+            'event': 'agenda@event-bar-slug',
+            'error': 'No agenda pricing found for event agenda@event-bar-slug',
+        },
+    ]
+
+    # ok
+    agenda_pricing.flat_fee_schedule = False
+    agenda_pricing.save()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
     assert resp.json['data'] == [
         {'event': 'agenda2@event-baz-slug', 'pricing_data': {'foo': 'baz'}},
         {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
@@ -345,3 +477,353 @@ def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_event
         {'event': 'agenda2@event-baz-slug', 'error': {'foo': 'error'}},
         {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
     ]
+
+
+@mock.patch('lingo.api.serializers.get_subscriptions')
+@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
+def test_pricing_compute_for_flat_fee_schedule_with_subscription(
+    mock_pricing_data, mock_subscriptions, app, user
+):
+    agenda = Agenda.objects.create(label='Foo bar')
+    pricing = Pricing.objects.create(label='Foo bar')
+    agenda_pricing = AgendaPricing.objects.create(
+        label='Foo bar pricing',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2021, month=10, day=1),
+        flat_fee_schedule=True,
+        subscription_required=True,
+    )
+    agenda_pricing.agendas.add(agenda)
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    # no subscription
+    mock_subscriptions.return_value = []
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-01',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+        status=400,
+    )
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == 'invalid payload'
+    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
+    assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')]
+
+    # no matching subscription
+    mock_subscriptions.reset_mock()
+    mock_subscriptions.return_value = [
+        {
+            'date_start': '2021-08-01',
+            'date_end': '2021-09-01',
+        },
+        {
+            'date_start': '2021-10-01',
+            'date_end': '2021-11-01',
+        },
+    ]
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+        status=400,
+    )
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == 'invalid payload'
+    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
+    assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')]
+
+    # no agenda_pricing found
+    agenda_pricing.delete()
+    agenda_pricing = AgendaPricing.objects.create(
+        pricing=pricing,
+        # bad dates
+        date_start=datetime.date(year=2021, month=8, day=1),
+        date_end=datetime.date(year=2021, month=9, day=1),
+    )
+    agenda_pricing.agendas.add(agenda)
+    agenda_pricing = AgendaPricing.objects.create(
+        pricing=pricing,
+        # bad dates
+        date_start=datetime.date(year=2021, month=9, day=3),
+        date_end=datetime.date(year=2021, month=10, day=1),
+    )
+    agenda_pricing.agendas.add(agenda)
+    mock_subscriptions.return_value = [
+        {
+            'date_start': '2021-08-01',
+            'date_end': '2021-09-01',
+        },
+        {
+            'date_start': '2021-09-02',
+            'date_end': '2021-09-03',
+        },
+        {
+            'date_start': '2021-10-01',
+            'date_end': '2021-11-01',
+        },
+    ]
+    mock_pricing_data.return_value = {'foo': 'bar'}
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {
+        'agenda': 'foo-bar',
+        'error': 'No agenda pricing found for agenda foo-bar',
+    }
+
+    agenda_pricing.delete()
+    agenda_pricing = AgendaPricing.objects.create(
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2021, month=10, day=1),
+        flat_fee_schedule=False,  # wrong config
+        subscription_required=True,
+    )
+    agenda_pricing.agendas.add(agenda)
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {
+        'agenda': 'foo-bar',
+        'error': 'No agenda pricing found for agenda foo-bar',
+    }
+
+    agenda_pricing.flat_fee_schedule = True
+    agenda_pricing.subscription_required = False  # wrong config
+    agenda_pricing.save()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {
+        'agenda': 'foo-bar',
+        'error': 'No agenda pricing found for agenda foo-bar',
+    }
+
+    # ok
+    agenda_pricing.subscription_required = True
+    agenda_pricing.save()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {'agenda': 'foo-bar', 'pricing_data': {'foo': 'bar'}}
+    assert mock_pricing_data.call_args_list == [
+        mock.call(
+            request=mock.ANY,
+            pricing_date=datetime.date(2021, 9, 1),
+            subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
+            user_external_id='user:1',
+            adult_external_id='adult:1',
+        ),
+    ]
+
+    # get_pricing_data with error
+    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {
+        'agenda': 'foo-bar',
+        'error': 'PricingError',
+        'error_details': {'foo': 'error'},
+    }
+
+    # check with billing dates
+    mock_pricing_data.return_value = {'foo': 'bar'}
+    agenda_pricing.billingdates.create(
+        date_start=datetime.date(2021, 9, 1),
+        label='Foo 1',
+    )
+    agenda_pricing.billingdates.create(
+        date_start=datetime.date(2021, 9, 15),
+        label='Foo 2',
+    )
+    mock_subscriptions.return_value = [
+        {
+            'date_start': '2021-09-01',
+            'date_end': '2021-09-15',
+        },
+    ]
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-16',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+        status=400,
+    )
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == 'invalid payload'
+    assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar']
+    mock_subscriptions.return_value = [
+        {
+            'date_start': '2021-09-15',
+            'date_end': '2021-09-16',
+        },
+    ]
+    mock_pricing_data.reset_mock()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-16',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert mock_pricing_data.call_args_list == [
+        mock.call(
+            request=mock.ANY,
+            pricing_date=datetime.date(2021, 9, 15),
+            subscription={'date_start': '2021-09-15', 'date_end': '2021-09-16'},
+            user_external_id='user:1',
+            adult_external_id='adult:1',
+        ),
+    ]
+    mock_subscriptions.return_value = [
+        {
+            'date_start': '2021-09-30',
+            'date_end': '2021-10-01',
+        },
+    ]
+    mock_pricing_data.reset_mock()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda': 'foo-bar',
+            'start_date': '2021-09-16',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert mock_pricing_data.call_args_list == [
+        mock.call(
+            request=mock.ANY,
+            pricing_date=datetime.date(2021, 9, 15),
+            subscription={'date_start': '2021-09-30', 'date_end': '2021-10-01'},
+            user_external_id='user:1',
+            adult_external_id='adult:1',
+        ),
+    ]
+
+
+@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
+def test_pricing_compute_for_flat_fee_schedule_without_subscription(mock_pricing_data, app, user):
+    pricing = Pricing.objects.create(label='Foo bar')
+    agenda_pricing = AgendaPricing.objects.create(
+        label='Foo bar pricing',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2021, month=10, day=1),
+        flat_fee_schedule=True,
+        subscription_required=False,
+    )
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    mock_pricing_data.return_value = {'foo': 'bar'}
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda_pricing': 'foo-bar-pricing',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {'agenda_pricing': 'foo-bar-pricing', 'pricing_data': {'foo': 'bar'}}
+    assert mock_pricing_data.call_args_list == [
+        mock.call(
+            request=mock.ANY,
+            pricing_date=datetime.date(2021, 9, 1),
+            subscription=None,
+            user_external_id='user:1',
+            adult_external_id='adult:1',
+        ),
+    ]
+
+    # get_pricing_data with error
+    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda_pricing': 'foo-bar-pricing',
+            'start_date': '2021-09-02',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert resp.json['data'] == {
+        'agenda_pricing': 'foo-bar-pricing',
+        'error': 'PricingError',
+        'error_details': {'foo': 'error'},
+    }
+
+    # check with billing dates
+    mock_pricing_data.return_value = {'foo': 'bar'}
+    agenda_pricing.billingdates.create(
+        date_start=datetime.date(2021, 9, 1),
+        label='Foo 1',
+    )
+    agenda_pricing.billingdates.create(
+        date_start=datetime.date(2021, 9, 15),
+        label='Foo 2',
+    )
+    mock_pricing_data.reset_mock()
+    resp = app.get(
+        '/api/pricing/compute/',
+        params={
+            'agenda_pricing': 'foo-bar-pricing',
+            'start_date': '2021-09-16',
+            'user_external_id': 'user:1',
+            'adult_external_id': 'adult:1',
+        },
+    )
+    assert mock_pricing_data.call_args_list == [
+        mock.call(
+            request=mock.ANY,
+            pricing_date=datetime.date(2021, 9, 15),
+            subscription=None,
+            user_external_id='user:1',
+            adult_external_id='adult:1',
+        ),
+    ]
-- 
2.35.1

