pricing: rename & rewrite get_pricing_data method (#67675)

renamed into get_pricing_data_for_event, to prepare flat_fee_schedule
mode
This commit is contained in:
Lauréline Guérin 2022-07-28 10:36:42 +02:00
parent fc7dc98b1f
commit 5d43627364
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 97 additions and 69 deletions

View File

@ -22,7 +22,7 @@ from rest_framework.exceptions import ValidationError
from lingo.agendas.chrono import ChronoError, get_events, get_subscriptions
from lingo.agendas.models import Agenda
from lingo.pricing.models import AgendaPricing, PricingError
from lingo.pricing.models import AgendaPricing, AgendaPricingNotFound, PricingError
class CommaSeparatedStringField(serializers.ListField):
@ -119,10 +119,13 @@ class PricingComputeSerializer(serializers.Serializer):
event_slugs = sorted(self.serialized_events.keys())
for event_slug in event_slugs:
serialized_event = self.serialized_events[event_slug]
start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
agenda = self.agendas[serialized_event['agenda']]
try:
pricing_data = AgendaPricing.get_pricing_data(
agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
pricing_data = agenda_pricing.get_pricing_data_for_event(
request=request,
agenda=self.agendas[serialized_event['agenda']],
agenda=agenda,
event=serialized_event,
subscription=self.event_subscriptions[event_slug],
check_status={
@ -139,6 +142,10 @@ class PricingComputeSerializer(serializers.Serializer):
'pricing_data': pricing_data,
}
)
except AgendaPricingNotFound:
result.append(
{'event': event_slug, 'error': _('No agenda pricing found for event %s') % event_slug}
)
except PricingError as e:
result.append({'event': event_slug, 'error': e.details})

View File

@ -389,10 +389,9 @@ class PricingTestToolForm(forms.Form):
if not self.serialized_event or not self.serialized_subscription:
return
try:
return AgendaPricing.get_pricing_data(
return self.agenda_pricing.get_pricing_data_for_event(
request=self.request,
agenda=self.agenda,
agenda_pricing=self.agenda_pricing,
event=self.serialized_event,
subscription=self.serialized_subscription,
check_status={

View File

@ -16,7 +16,6 @@
import copy
import dataclasses
import datetime
import decimal
from typing import List
@ -400,33 +399,24 @@ class AgendaPricing(models.Model):
return created, agenda_pricing
@staticmethod
def get_pricing_data(
request,
agenda,
event,
subscription,
check_status,
booking,
user_external_id,
adult_external_id,
agenda_pricing=None,
def get_pricing_data_for_event(
self, request, agenda, event, subscription, check_status, booking, user_external_id, adult_external_id
):
agenda_pricing = agenda_pricing or AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
# compute pricing for an event
data = {
'event': event,
'subscription': subscription,
'booking': booking,
}
context = agenda_pricing.get_pricing_context(
context = self.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(agenda=agenda, check_status=check_status)
return agenda_pricing.aggregate_pricing_data(
pricing, criterias = self.compute_pricing(context=context)
modifier = self.get_booking_modifier(agenda=agenda, check_status=check_status)
return self.aggregate_pricing_data(
pricing=pricing, criterias=criterias, context=context, modifier=modifier
)
@ -446,12 +436,11 @@ class AgendaPricing(models.Model):
}
@staticmethod
def get_agenda_pricing(agenda, event):
start_datetime = datetime.datetime.fromisoformat(event['start_datetime'])
def get_agenda_pricing(agenda, start_date):
try:
return agenda.agendapricings.get(
date_start__lte=start_datetime,
date_end__gt=start_datetime,
date_start__lte=start_date,
date_end__gt=start_date,
)
except (AgendaPricing.DoesNotExist, AgendaPricing.MultipleObjectsReturned):
raise AgendaPricingNotFound

View File

@ -1,10 +1,11 @@
import datetime
from unittest import mock
import pytest
from lingo.agendas.chrono import ChronoError
from lingo.agendas.models import Agenda
from lingo.pricing.models import PricingError
from lingo.pricing.models import AgendaPricing, Pricing, PricingError
pytestmark = pytest.mark.django_db
@ -145,12 +146,13 @@ 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')
def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app, user):
@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):
agenda = Agenda.objects.create(label='Agenda')
agenda2 = Agenda.objects.create(label='Agenda2')
app.authorization = ('Basic', ('john.doe', 'password'))
# no subscription
mock_events.return_value = [
{'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'}
]
@ -169,9 +171,10 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
assert resp.json['errors']['user_external_id'] == [
'No subscription found for event agenda@event-bar-slug'
]
assert mock_pricing_data.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
# no matching subscription
mock_subscriptions.reset_mock()
mock_subscriptions.return_value = [
{
@ -197,9 +200,10 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
assert resp.json['errors']['user_external_id'] == [
'No subscription found for event agenda@event-bar-slug'
]
assert mock_pricing_data.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
# no matching subscription
mock_events.return_value = [
{'start_datetime': '2021-09-02T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'},
{'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda2', 'slug': 'event-baz-slug'},
@ -229,12 +233,13 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
assert resp.json['errors']['user_external_id'] == [
'No subscription found for event agenda2@event-baz-slug'
]
assert mock_pricing_data.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
assert mock_subscriptions.call_args_list == [
mock.call('agenda', 'user:1'),
mock.call('agenda2', 'user:1'),
]
# no agenda_pricing found
mock_subscriptions.return_value = [
{
'date_start': '2021-08-01',
@ -249,7 +254,7 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
'date_end': '2021-09-03',
},
]
mock_pricing_data.side_effect = [
mock_pricing_data_event.side_effect = [
{'foo': 'baz'},
{'foo': 'bar'},
]
@ -261,11 +266,38 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
'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
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),
)
agenda_pricing.agendas.add(agenda, agenda2)
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'}},
]
assert mock_pricing_data.call_args_list == [
assert mock_pricing_data_event.call_args_list == [
mock.call(
request=mock.ANY,
agenda=agenda2,
@ -296,7 +328,8 @@ def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app
),
]
mock_pricing_data.side_effect = [
# get_pricing_data_for_event with error
mock_pricing_data_event.side_effect = [
PricingError(details={'foo': 'error'}),
{'foo': 'bar'},
]

View File

@ -821,8 +821,10 @@ def test_detail_agenda_pricing_1_category(app, admin_user):
@mock.patch('lingo.pricing.forms.get_event')
@mock.patch('lingo.pricing.forms.get_subscriptions')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, mock_event, app, admin_user):
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
def test_detail_agenda_pricing_test_tool(
mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
@ -847,7 +849,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions,
assert resp.context['test_tool_form'].errors['event_slug'] == [
'This event takes place outside the period covered by this pricing'
]
assert mock_pricing_data.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
mock_event.return_value = {'start_datetime': '2021-10-01T12:00:00+02:00'}
resp = resp.form.submit().follow()
assert 'Computed pricing data' not in resp
@ -870,7 +872,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions,
mock_event.reset_mock()
resp.form['event_slug'] = 'foo-bar@foo'
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
assert 'Computed pricing data' not in resp
assert resp.context['test_tool_form'].errors['user_external_id'] == [
'No subscription found for this event'
@ -906,14 +908,13 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions,
'date_end': '2021-09-03',
},
]
mock_pricing_data.return_value = {'foo': 'bar', 'pricing': Decimal('42')}
mock_pricing_data_event.return_value = {'foo': 'bar', 'pricing': Decimal('42')}
resp = resp.form.submit().follow()
assert 'Computed pricing data' in resp
assert mock_pricing_data.call_args_list == [
assert mock_pricing_data_event.call_args_list == [
mock.call(
request=mock.ANY,
agenda=agenda,
agenda_pricing=agenda_pricing,
event={'start_datetime': '2021-09-01T12:00:00+02:00'},
subscription={'date_start': '2021-09-01', 'date_end': '2021-09-02'},
check_status={'status': 'presence', 'check_type': None},
@ -925,7 +926,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions,
assert '<p>Pricing: 42.00</p>' in resp
assert '<pre>{&#39;foo&#39;: &#39;bar&#39;, &#39;pricing&#39;: Decimal(&#39;42&#39;)}</pre>' in resp
mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
mock_pricing_data_event.side_effect = PricingError(details={'foo': 'error'})
resp = resp.form.submit().follow()
assert 'Computed pricing data' in resp
assert '<pre>{&#39;error&#39;: {&#39;foo&#39;: &#39;error&#39;}}</pre>' in resp
@ -982,9 +983,9 @@ def test_detail_agenda_pricing_test_tool_subscription_error(mock_subscriptions,
@mock.patch('lingo.pricing.forms.get_event')
@mock.patch('lingo.pricing.forms.get_subscriptions')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
def test_detail_agenda_pricing_test_tool_booking_status(
mock_pricing_data, mock_subscriptions, mock_event, app, admin_user
mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
@ -1032,28 +1033,31 @@ def test_detail_agenda_pricing_test_tool_booking_status(
('absence', False, 'Absence'),
('absence::foo-absence-reason', False, 'Absence (Foo absence reason)'),
]
assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
'check_type': None,
'status': 'presence',
}
mock_pricing_data.reset_mock()
mock_pricing_data_event.reset_mock()
resp.form['booking_status'] = 'presence::foo-presence-reason'
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
'check_type': 'foo-presence-reason',
'status': 'presence',
}
mock_pricing_data.reset_mock()
mock_pricing_data_event.reset_mock()
resp.form['booking_status'] = 'absence'
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list[0][1]['check_status'] == {'check_type': None, 'status': 'absence'}
assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
'check_type': None,
'status': 'absence',
}
mock_pricing_data.reset_mock()
mock_pricing_data_event.reset_mock()
resp.form['booking_status'] = 'absence::foo-absence-reason'
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
'check_type': 'foo-absence-reason',
'status': 'absence',
}

View File

@ -283,13 +283,11 @@ def test_agenda_pricing_duplicate_slugs():
def test_get_agenda_pricing():
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
event = {
'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat(),
}
start_date = datetime.datetime(2021, 9, 15)
# not found
with pytest.raises(AgendaPricingNotFound):
AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
# ok
agenda_pricing = AgendaPricing.objects.create(
@ -298,7 +296,7 @@ def test_get_agenda_pricing():
date_end=datetime.date(year=2021, month=10, day=1),
)
agenda_pricing.agendas.add(agenda)
assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
assert AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date) == agenda_pricing
# more than one matching
agenda_pricing = AgendaPricing.objects.create(
@ -308,39 +306,37 @@ def test_get_agenda_pricing():
)
agenda_pricing.agendas.add(agenda)
with pytest.raises(AgendaPricingNotFound):
AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
@pytest.mark.parametrize(
'event_date, found',
[
# just before first day
((2021, 8, 31, 12, 00), False),
((2021, 8, 31), False),
# first day
((2021, 9, 1, 12, 00), True),
((2021, 9, 1), True),
# last day
((2021, 9, 30, 12, 00), True),
((2021, 9, 30), True),
# just after last day
((2021, 10, 1, 12, 00), False),
((2021, 10, 1), False),
],
)
def test_get_agenda_pricing_event_date(event_date, found):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
event = {
'start_datetime': make_aware(datetime.datetime(*event_date)).isoformat(),
}
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),
)
agenda_pricing.agendas.add(agenda)
start_date = datetime.date(*event_date)
if found:
assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
assert AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date) == agenda_pricing
else:
with pytest.raises(AgendaPricingNotFound):
AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
@ -931,7 +927,7 @@ def test_get_booking_modifier_booking_presence():
}
def test_get_pricing_data(context):
def test_get_pricing_data_for_event(context):
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)
@ -953,7 +949,7 @@ def test_get_pricing_data(context):
},
)
agenda_pricing.agendas.add(agenda)
assert AgendaPricing.get_pricing_data(
assert agenda_pricing.get_pricing_data_for_event(
request=context['request'],
agenda=agenda,
event={'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat()},