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', + ), + ]