pricing: adapt test tool for flat fee schedule mode (#67675)

This commit is contained in:
Lauréline Guérin 2022-07-28 16:24:13 +02:00
parent 9531507e12
commit bbe4dce2cf
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 310 additions and 35 deletions

View File

@ -292,6 +292,9 @@ class PricingMatrixForm(forms.Form):
class PricingTestToolForm(forms.Form):
agenda = forms.ModelChoiceField(label=_('Agenda'), empty_label=None, queryset=Agenda.objects.none())
event_slug = forms.CharField(label=_('Event identifier'))
billing_date = forms.ModelChoiceField(
label=_('Billing date'), empty_label=None, queryset=BillingDate.objects.none()
)
user_external_id = forms.CharField(label=_('User external identifier'))
adult_external_id = forms.CharField(label=_('Adult external identifier'))
booking_status = forms.ChoiceField(label=_('Booking status'), choices=[])
@ -307,7 +310,25 @@ class PricingTestToolForm(forms.Form):
self.check_type_slug = None
self.booking_status = None
super().__init__(*args, **kwargs)
self.fields['agenda'].queryset = self.agenda_pricing.agendas.all()
if self.agenda_pricing.subscription_required:
self.fields['agenda'].queryset = self.agenda_pricing.agendas.all()
else:
del self.fields['agenda']
if self.agenda_pricing.flat_fee_schedule:
del self.fields['event_slug']
del self.fields['booking_status']
self.init_billing_date()
else:
del self.fields['billing_date']
self.init_booking_status()
def init_agenda(self, agenda_id):
try:
self.agenda = self.agenda_pricing.agendas.get(pk=agenda_id)
except Agenda.DoesNotExist:
pass
def init_booking_status(self):
presence_check_types = (
self.agenda.check_type_group.check_types.presences()
if self.agenda and self.agenda.check_type_group
@ -330,11 +351,12 @@ class PricingTestToolForm(forms.Form):
]
self.fields['booking_status'].choices = status_choices
def init_agenda(self, agenda_id):
try:
self.agenda = self.agenda_pricing.agendas.get(pk=agenda_id)
except Agenda.DoesNotExist:
pass
def init_billing_date(self):
billing_dates = self.agenda_pricing.billingdates.order_by('date_start')
if not billing_dates:
del self.fields['billing_date']
return
self.fields['billing_date'].queryset = billing_dates
def clean_event_slug(self):
event_slug = self.cleaned_data['event_slug']
@ -359,9 +381,7 @@ class PricingTestToolForm(forms.Form):
self.booking_status, self.check_type_slug = original_booking_status.split('::')
return original_booking_status
def get_subscription(self, user_external_id):
start_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date()
end_date = start_date + datetime.timedelta(days=1)
def get_subscription(self, user_external_id, start_date, end_date):
try:
subscriptions = get_subscriptions(self.agenda.slug, user_external_id)
except ChronoError as e:
@ -375,33 +395,70 @@ class PricingTestToolForm(forms.Form):
if sub_end_date <= start_date:
continue
return subscription
self.add_error('user_external_id', _('No subscription found for this event'))
error_message = _('No subscription found for this event')
if self.agenda_pricing.flat_fee_schedule:
error_message = _('No subscription found for this period')
self.add_error('user_external_id', error_message)
def clean(self):
super().clean()
if self.cleaned_data.get('user_external_id') and self.serialized_event:
user_external_id = self.cleaned_data['user_external_id']
self.serialized_subscription = self.get_subscription(user_external_id)
user_external_id = self.cleaned_data.get('user_external_id')
start_date = None
end_date = None
if self.agenda_pricing.flat_fee_schedule and self.agenda_pricing.subscription_required:
if self.cleaned_data.get('billing_date'):
start_date = self.cleaned_data['billing_date'].date_start
next_billing_date = (
self.fields['billing_date'].queryset.filter(date_start__gt=start_date).first()
)
end_date = next_billing_date.date_start if next_billing_date else self.agenda_pricing.date_end
else:
start_date = self.agenda_pricing.date_start
end_date = self.agenda_pricing.date_end
elif self.serialized_event:
start_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date()
end_date = start_date + datetime.timedelta(days=1)
if user_external_id and start_date and end_date:
self.serialized_subscription = self.get_subscription(user_external_id, start_date, end_date)
def compute(self):
try:
if self.agenda_pricing.flat_fee_schedule:
return self.compute_for_flat_fee_schedule()
return self.compute_for_event()
except PricingError as e:
return {
'error': type(e),
'error_details': e.details,
}
def compute_for_flat_fee_schedule(self):
if self.agenda_pricing.subscription_required and not self.serialized_subscription:
return
return self.agenda_pricing.get_pricing_data(
request=self.request,
subscription=self.serialized_subscription,
user_external_id=self.cleaned_data['user_external_id'],
adult_external_id=self.cleaned_data['adult_external_id'],
)
def compute_for_event(self):
if not self.serialized_event or not self.serialized_subscription:
return
try:
return self.agenda_pricing.get_pricing_data_for_event(
request=self.request,
agenda=self.agenda,
event=self.serialized_event,
subscription=self.serialized_subscription,
check_status={
'status': self.booking_status,
'check_type': self.check_type_slug,
},
booking={},
user_external_id=self.cleaned_data['user_external_id'],
adult_external_id=self.cleaned_data['adult_external_id'],
)
except PricingError as e:
return {'error': e.details}
return self.agenda_pricing.get_pricing_data_for_event(
request=self.request,
agenda=self.agenda,
event=self.serialized_event,
subscription=self.serialized_subscription,
check_status={
'status': self.booking_status,
'check_type': self.check_type_slug,
},
booking={},
user_external_id=self.cleaned_data['user_external_id'],
adult_external_id=self.cleaned_data['adult_external_id'],
)
class NewCheckTypeForm(forms.ModelForm):

View File

@ -694,6 +694,9 @@ class BillingDate(models.Model):
date_start = models.DateField(_('Billing start date'))
label = models.CharField(_('Label'), max_length=150)
def __str__(self):
return '%s (%s)' % (self.date_start.strftime('%d/%m/%Y'), self.label)
def export_json(self):
return {
'date_start': self.date_start.strftime('%Y-%m-%d'),

View File

@ -74,6 +74,7 @@
<div aria-labelledby="tab-debug" hidden id="panel-debug" role="tabpanel" tabindex="0">
<form method="get" enctype="multipart/form-data" action="{% url 'lingo-manager-agenda-pricing-test-tool' object.pk %}">
{{ test_tool_form.as_p }}
{% if not object.flat_fee_schedule %}
<script>
$(function() {
var presences = {};
@ -109,6 +110,7 @@
});
});
</script>
{% endif %}
<div class="buttons">
<button class="submit-button">{% trans "Compute" %}</button>
</div>
@ -133,7 +135,7 @@
{% for billing_date in billing_dates %}
<li>
<a rel="popup" href="{% url 'lingo-manager-agenda-pricing-billing-date-edit' object.pk billing_date.pk %}">
{{ billing_date.date_start|date:'d/m/Y' }} ({{ billing_date.label }})
{{ billing_date }}
</a>
<a class="delete" rel="popup" href="{% url 'lingo-manager-agenda-pricing-billing-date-delete' object.pk billing_date.pk %}">{% trans "remove"%}</a>
</li>

View File

@ -822,7 +822,7 @@ 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_for_event')
def test_detail_agenda_pricing_test_tool(
def test_detail_agenda_pricing_test_tool_for_event(
mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
@ -836,6 +836,7 @@ def test_detail_agenda_pricing_test_tool(
app = login(app)
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
assert 'billing_date' not in resp.context['test_tool_form'].fields
assert 'Computed pricing data' not in resp
# check event date
@ -921,12 +922,15 @@ def test_detail_agenda_pricing_test_tool(
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
assert '&#39;error&#39;: &lt;class &#39;lingo.pricing.models.PricingError&#39;&gt;' in resp
assert '&#39;error_details&#39;: {&#39;foo&#39;: &#39;error&#39;}' in resp
@mock.patch('lingo.pricing.forms.get_event')
@mock.patch('lingo.pricing.forms.get_subscriptions')
def test_detail_agenda_pricing_test_tool_event_error(mock_subscriptions, mock_event, app, admin_user):
def test_detail_agenda_pricing_test_tool_for_event_event_error(
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(
@ -951,7 +955,9 @@ def test_detail_agenda_pricing_test_tool_event_error(mock_subscriptions, mock_ev
@mock.patch('lingo.pricing.forms.get_event')
@mock.patch('lingo.pricing.forms.get_subscriptions')
def test_detail_agenda_pricing_test_tool_subscription_error(mock_subscriptions, mock_event, app, admin_user):
def test_detail_agenda_pricing_test_tool_for_event_subscription_error(
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(
@ -978,7 +984,7 @@ 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_for_event')
def test_detail_agenda_pricing_test_tool_booking_status(
def test_detail_agenda_pricing_test_tool_for_event_booking_status(
mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
@ -1058,6 +1064,213 @@ def test_detail_agenda_pricing_test_tool_booking_status(
}
@mock.patch('lingo.pricing.forms.get_subscriptions')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
def test_detail_agenda_pricing_test_tool_for_flat_fee_schedule(
mock_pricing_data, mock_subscriptions, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
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,
subscription_required=True,
)
agenda_pricing.agendas.add(agenda)
app = login(app)
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
assert 'event_slug' not in resp.context['test_tool_form'].fields
assert 'booking_status' not in resp.context['test_tool_form'].fields
assert 'billing_date' not in resp.context['test_tool_form'].fields
assert 'Computed pricing data' not in resp
# check subscriptions dates
mock_subscriptions.return_value = []
resp.form['agenda'] = agenda.pk
resp.form['user_external_id'] = 'user:1'
resp.form['adult_external_id'] = 'adult:1'
resp = resp.form.submit().follow()
assert mock_pricing_data.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 period'
]
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 = resp.form.submit().follow()
assert 'Computed pricing data' not in resp
assert resp.context['test_tool_form'].errors['user_external_id'] == [
'No subscription found for this period'
]
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', 'pricing': Decimal('42')}
resp = resp.form.submit().follow()
assert 'Computed pricing data' in resp
assert mock_pricing_data.call_args_list == [
mock.call(
request=mock.ANY,
subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
user_external_id='user:1',
adult_external_id='adult:1',
)
]
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'})
resp = resp.form.submit().follow()
assert 'Computed pricing data' in resp
assert '&#39;error&#39;: &lt;class &#39;lingo.pricing.models.PricingError&#39;&gt;' in resp
assert '&#39;error_details&#39;: {&#39;foo&#39;: &#39;error&#39;}' in resp
billing_date1 = agenda_pricing.billingdates.create(
date_start=datetime.date(2021, 9, 1),
label='Foo 1',
)
billing_date2 = agenda_pricing.billingdates.create(
date_start=datetime.date(2021, 9, 15),
label='Foo 2',
)
# check with billing dates
mock_pricing_data.reset_mock()
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
resp.form['agenda'] = agenda.pk
resp.form['billing_date'] = billing_date1.pk
resp.form['user_external_id'] = 'user:1'
resp.form['adult_external_id'] = 'adult:1'
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == [
mock.call(
request=mock.ANY,
subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
user_external_id='user:1',
adult_external_id='adult:1',
)
]
mock_pricing_data.reset_mock()
resp.form['billing_date'] = billing_date2.pk
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == []
mock_subscriptions.return_value = [
{
'date_start': '2021-09-01',
'date_end': '2021-09-15',
},
]
mock_pricing_data.reset_mock()
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == []
mock_subscriptions.return_value = [
{
'date_start': '2021-09-15',
'date_end': '2021-09-16',
},
]
mock_pricing_data.reset_mock()
resp = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == [
mock.call(
request=mock.ANY,
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 = resp.form.submit().follow()
assert mock_pricing_data.call_args_list == [
mock.call(
request=mock.ANY,
subscription={'date_start': '2021-09-30', 'date_end': '2021-10-01'},
user_external_id='user:1',
adult_external_id='adult:1',
)
]
# check with subscription_required False
agenda_pricing.subscription_required = False
agenda_pricing.save()
mock_pricing_data.reset_mock()
mock_subscriptions.reset_mock()
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
assert 'agenda' not in resp.context['test_tool_form'].fields
resp.form['billing_date'] = billing_date1.pk
resp.form['user_external_id'] = 'user:1'
resp.form['adult_external_id'] = 'adult:1'
resp = resp.form.submit().follow()
assert mock_subscriptions.call_args_list == []
assert mock_pricing_data.call_args_list == [
mock.call(
request=mock.ANY,
subscription=None,
user_external_id='user:1',
adult_external_id='adult:1',
)
]
@mock.patch('lingo.pricing.forms.get_subscriptions')
def test_detail_agenda_pricing_test_tool_for_flat_fee_schedule_subscription_error(
mock_subscriptions, app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar')
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,
)
agenda_pricing.agendas.add(agenda)
mock_subscriptions.side_effect = ChronoError('foo bar')
app = login(app)
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
resp.form['agenda'] = agenda.pk
resp.form['user_external_id'] = 'user:1'
resp.form['adult_external_id'] = 'adult:1'
resp = resp.form.submit().follow()
assert resp.context['test_tool_form'].errors['user_external_id'] == ['foo bar']
def test_edit_agenda_pricing_matrix_3_categories(app, admin_user):
category1 = CriteriaCategory.objects.create(label='Cat 1')
criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1)