Tarification: appliquer un taux de réduction au tarif trouvé dans la grille tarifaire (#81231) #100
|
@ -0,0 +1,30 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pricing', '0012_payer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='agendapricing',
|
||||
name='min_pricing',
|
||||
field=models.DecimalField(
|
||||
blank=True, decimal_places=2, max_digits=9, null=True, verbose_name='Minimal pricing'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pricing',
|
||||
name='reduction_rate',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text=(
|
||||
'The result is expressed as a percentage, and must be between 0 and 100. '
|
||||
'Leave it empty to apply the pricing without reduction.'
|
||||
),
|
||||
max_length=1000,
|
||||
verbose_name='Reduction rate (template)',
|
||||
),
|
||||
),
|
||||
]
|
|
@ -54,6 +54,18 @@ class PricingDataFormatError(PricingError):
|
|||
pass
|
||||
|
||||
|
||||
class PricingReductionRateError(PricingError):
|
||||
pass
|
||||
|
||||
|
||||
class PricingReductionRateFormatError(PricingError):
|
||||
pass
|
||||
|
||||
|
||||
class PricingReductionRateValueError(PricingError):
|
||||
pass
|
||||
|
||||
|
||||
class PricingUnknownCheckStatusError(PricingError):
|
||||
pass
|
||||
|
||||
|
@ -186,6 +198,15 @@ class Pricing(models.Model):
|
|||
)
|
||||
criterias = models.ManyToManyField(Criteria)
|
||||
extra_variables = models.JSONField(blank=True, default=dict)
|
||||
reduction_rate = models.CharField(
|
||||
_('Reduction rate (template)'),
|
||||
max_length=1000,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
'The result is expressed as a percentage, and must be between 0 and 100. '
|
||||
'Leave it empty to apply the pricing without reduction.'
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['label']
|
||||
|
@ -340,6 +361,9 @@ class AgendaPricing(models.Model):
|
|||
flat_fee_schedule = models.BooleanField(_('Flat fee schedule'), default=False)
|
||||
subscription_required = models.BooleanField(_('Subscription is required'), default=True)
|
||||
pricing_data = models.JSONField(null=True)
|
||||
min_pricing = models.DecimalField(
|
||||
_('Minimal pricing'), max_digits=9, decimal_places=2, blank=True, null=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label or self.pricing.label
|
||||
|
@ -403,11 +427,19 @@ class AgendaPricing(models.Model):
|
|||
payer_external_id=payer_external_id,
|
||||
)
|
||||
pricing, criterias = self.compute_pricing(context=context)
|
||||
pricing, reduction_rate = self.apply_reduction_rate(
|
||||
pricing=pricing,
|
||||
request=request,
|
||||
context=context,
|
||||
user_external_id=user_external_id,
|
||||
payer_external_id=payer_external_id,
|
||||
)
|
||||
return {
|
||||
'pricing': pricing,
|
||||
'calculation_details': {
|
||||
'pricing': pricing,
|
||||
'criterias': criterias,
|
||||
'reduction_rate': reduction_rate,
|
||||
'context': context,
|
||||
},
|
||||
}
|
||||
|
@ -428,12 +460,23 @@ class AgendaPricing(models.Model):
|
|||
payer_external_id=payer_external_id,
|
||||
)
|
||||
pricing, criterias = self.compute_pricing(context=context)
|
||||
pricing, reduction_rate = self.apply_reduction_rate(
|
||||
pricing=pricing,
|
||||
request=request,
|
||||
context=context,
|
||||
user_external_id=user_external_id,
|
||||
payer_external_id=payer_external_id,
|
||||
)
|
||||
modifier = self.get_booking_modifier(agenda=agenda, check_status=check_status)
|
||||
return self.aggregate_pricing_data(
|
||||
pricing=pricing, criterias=criterias, context=context, modifier=modifier
|
||||
pricing=pricing,
|
||||
criterias=criterias,
|
||||
reduction_rate=reduction_rate,
|
||||
context=context,
|
||||
modifier=modifier,
|
||||
)
|
||||
|
||||
def aggregate_pricing_data(self, pricing, criterias, context, modifier):
|
||||
def aggregate_pricing_data(self, pricing, criterias, reduction_rate, context, modifier):
|
||||
if modifier['modifier_type'] == 'fixed':
|
||||
pricing_amount = modifier['modifier_fixed']
|
||||
else:
|
||||
|
@ -443,6 +486,7 @@ class AgendaPricing(models.Model):
|
|||
'calculation_details': {
|
||||
'pricing': pricing,
|
||||
'criterias': criterias,
|
||||
'reduction_rate': reduction_rate,
|
||||
'context': context,
|
||||
},
|
||||
'booking_details': modifier,
|
||||
|
@ -535,6 +579,55 @@ class AgendaPricing(models.Model):
|
|||
|
||||
return pricing, criterias
|
||||
|
||||
def compute_reduction_rate(self, request, original_context, user_external_id, payer_external_id):
|
||||
context = RequestContext(request)
|
||||
context.push(original_context)
|
||||
context.push({'user_external_id': user_external_id, 'payer_external_id': payer_external_id})
|
||||
if ':' in user_external_id:
|
||||
context['user_external_raw_id'] = user_external_id.split(':')[1]
|
||||
if ':' in payer_external_id:
|
||||
context['payer_external_raw_id'] = payer_external_id.split(':')[1]
|
||||
try:
|
||||
reduction_rate = Template(self.pricing.reduction_rate).render(context)
|
||||
except (TemplateSyntaxError, VariableDoesNotExist):
|
||||
raise PricingReductionRateError()
|
||||
|
||||
try:
|
||||
reduction_rate = decimal.Decimal(reduction_rate)
|
||||
except (decimal.InvalidOperation, ValueError, TypeError):
|
||||
raise PricingReductionRateFormatError(
|
||||
details={'reduction_rate': reduction_rate, 'wanted': 'decimal'}
|
||||
)
|
||||
|
||||
if reduction_rate < 0 or reduction_rate > 100:
|
||||
raise PricingReductionRateValueError(details={'reduction_rate': reduction_rate})
|
||||
|
||||
return reduction_rate
|
||||
|
||||
def apply_reduction_rate(self, pricing, request, context, user_external_id, payer_external_id):
|
||||
if not self.pricing.reduction_rate:
|
||||
return pricing, {}
|
||||
|
||||
reduction_rate = self.compute_reduction_rate(
|
||||
request=request,
|
||||
original_context=context,
|
||||
user_external_id=user_external_id,
|
||||
payer_external_id=payer_external_id,
|
||||
)
|
||||
|
||||
new_pricing = pricing * (100 - reduction_rate) / 100
|
||||
adjusted_pricing = new_pricing
|
||||
if self.min_pricing is not None:
|
||||
adjusted_pricing = max(adjusted_pricing, self.min_pricing)
|
||||
|
||||
return adjusted_pricing, {
|
||||
'computed_pricing': pricing,
|
||||
'reduction_rate': reduction_rate,
|
||||
'reduced_pricing': new_pricing,
|
||||
'min_pricing': self.min_pricing,
|
||||
'bounded_pricing': adjusted_pricing,
|
||||
}
|
||||
|
||||
def get_booking_modifier(self, agenda, check_status):
|
||||
status = check_status['status']
|
||||
if status not in ['error', 'not-booked', 'cancelled', 'presence', 'absence']:
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-options" aria-selected="true" id="tab-options" role="tab" tabindex="0">{% trans "Options" %}</button>
|
||||
{% if object.pricing.reduction_rate %}
|
||||
<button aria-controls="panel-pricing-options" aria-selected="false" id="tab-pricing-options" role="tab" tabindex="0">{% trans "Pricing options" %}</button>
|
||||
{% endif %}
|
||||
{% if object.subscription_required %}
|
||||
<button aria-controls="panel-agendas" aria-selected="false" id="tab-agendas" role="tab" tabindex="0">{% trans "Agendas" %}</button>
|
||||
{% endif %}
|
||||
|
@ -53,6 +56,17 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
{% if object.pricing.reduction_rate %}
|
||||
<div aria-labelledby="tab-pricing-options" hidden id="panel-pricing-options" role="tabpanel" tabindex="0">
|
||||
<ul>
|
||||
<li>{% trans "Minimal pricing:" %} {{ object.min_pricing|default_if_none:"" }}</li>
|
||||
</ul>
|
||||
<div class="panel--buttons">
|
||||
<a class="pk-button" rel="popup" href="{% url 'lingo-manager-agenda-pricing-pricingoptions-edit' object.pk %}">{% trans "Edit pricing options" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if object.subscription_required %}
|
||||
<div aria-labelledby="tab-agendas" hidden id="panel-agendas" role="tabpanel" tabindex="0">
|
||||
<ul class="objects-list single-links">
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "lingo/pricing/manager_agenda_pricing_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-agenda-pricing-pricingoptions-edit' object.pk %}">{% trans "Edit" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Edit pricing options" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'lingo-manager-agenda-pricing-detail' object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -26,13 +26,23 @@
|
|||
<div class="section">
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-variables" aria-selected="true" id="tab-variables" role="tab" tabindex="0">{% trans "Variables" %}</button>
|
||||
<button aria-controls="panel-options" aria-selected="true" id="tab-options" role="tab" tabindex="0">{% trans "Options" %}</button>
|
||||
<button aria-controls="panel-variables" aria-selected="false" id="tab-variables" role="tab" tabindex="-1">{% trans "Variables" %}</button>
|
||||
<button aria-controls="panel-criterias" aria-selected="false" id="tab-criterias" role="tab" tabindex="-1">{% trans "Criterias" %}</button>
|
||||
<button aria-controls="panel-usage" aria-selected="false" id="tab-usage" role="tab" tabindex="-1">{% trans "Used in agendas" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-variables" id="panel-variables" role="tabpanel" tabindex="0">
|
||||
<div aria-labelledby="tab-options" id="panel-options" role="tabpanel" tabindex="0">
|
||||
<ul>
|
||||
<li>{% trans "Reduction rate enabled:"%} {{ object.reduction_rate|yesno }}</li>
|
||||
{% if object.reduction_rate %}
|
||||
<li>{% trans "Reduction rate (template):" %} <pre>{{ object.reduction_rate }}</pre></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-variables" hidden="" id="panel-variables" role="tabpanel" tabindex="0">
|
||||
{% if object.extra_variables %}
|
||||
<label>{% trans 'Extra variables:' %}</label>
|
||||
<dl>
|
||||
|
|
|
@ -192,6 +192,11 @@ urlpatterns = [
|
|||
views.agenda_pricing_test_tool,
|
||||
name='lingo-manager-agenda-pricing-test-tool',
|
||||
),
|
||||
path(
|
||||
'agenda-pricing/<int:pk>/pricing-options/',
|
||||
views.agenda_pricing_pricingoptions_edit,
|
||||
name='lingo-manager-agenda-pricing-pricingoptions-edit',
|
||||
),
|
||||
path(
|
||||
'agenda-pricing/<int:pk>/agenda/add/',
|
||||
views.agenda_pricing_agenda_add,
|
||||
|
|
|
@ -310,7 +310,7 @@ pricing_detail = PricingDetailView.as_view()
|
|||
class PricingEditView(UpdateView):
|
||||
template_name = 'lingo/pricing/manager_pricing_form.html'
|
||||
model = Pricing
|
||||
fields = ['label', 'slug']
|
||||
fields = ['label', 'slug', 'reduction_rate']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-pricing-detail', args=[self.object.pk])
|
||||
|
@ -396,7 +396,7 @@ class PricingVariableEdit(FormView):
|
|||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-pricing-detail', args=[self.object.pk])
|
||||
return '%s#open:variables' % reverse('lingo-manager-pricing-detail', args=[self.object.pk])
|
||||
|
||||
|
||||
pricing_variable_edit = PricingVariableEdit.as_view()
|
||||
|
@ -826,6 +826,23 @@ class AgendaPricingEditView(UpdateView):
|
|||
agenda_pricing_edit = AgendaPricingEditView.as_view()
|
||||
|
||||
|
||||
class AgendaPricingPricingOptionsEditView(UpdateView):
|
||||
template_name = 'lingo/pricing/manager_agenda_pricing_pricingoptions_form.html'
|
||||
model = AgendaPricing
|
||||
fields = ['min_pricing']
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().exclude(pricing__reduction_rate='')
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#open:pricing-options' % reverse(
|
||||
'lingo-manager-agenda-pricing-detail', args=[self.object.pk]
|
||||
)
|
||||
|
||||
|
||||
agenda_pricing_pricingoptions_edit = AgendaPricingPricingOptionsEditView.as_view()
|
||||
|
||||
|
||||
class AgendaPricingDeleteView(DeleteView):
|
||||
template_name = 'lingo/manager_confirm_delete.html'
|
||||
model = AgendaPricing
|
||||
|
|
|
@ -350,6 +350,46 @@ def test_edit_agenda_pricing_billing_date_end(app, admin_user):
|
|||
resp.form.submit().follow()
|
||||
|
||||
|
||||
def test_edit_agenda_pricing_pricingoptions(app, admin_user):
|
||||
pricing = Pricing.objects.create(label='Model')
|
||||
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),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
|
||||
assert pricing.reduction_rate == ''
|
||||
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
|
||||
assert 'Pricing options' not in resp
|
||||
app.get('/manage/pricing/agenda-pricing/%s/pricing-options/' % agenda_pricing.pk, status=404)
|
||||
|
||||
pricing.reduction_rate = 'foo'
|
||||
pricing.save()
|
||||
resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
|
||||
assert 'Pricing options' in resp
|
||||
resp = resp.click(href='/manage/pricing/agenda-pricing/%s/pricing-options/' % agenda_pricing.pk)
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/manage/pricing/agenda-pricing/%s/#open:pricing-options' % agenda_pricing.pk
|
||||
)
|
||||
agenda_pricing.refresh_from_db()
|
||||
assert agenda_pricing.min_pricing is None
|
||||
|
||||
resp = app.get('/manage/pricing/agenda-pricing/%s/pricing-options/' % agenda_pricing.pk)
|
||||
resp.form['min_pricing'] = 0
|
||||
resp = resp.form.submit()
|
||||
agenda_pricing.refresh_from_db()
|
||||
assert agenda_pricing.min_pricing == 0
|
||||
|
||||
resp = app.get('/manage/pricing/agenda-pricing/%s/pricing-options/' % agenda_pricing.pk)
|
||||
resp.form['min_pricing'] = 10
|
||||
resp = resp.form.submit()
|
||||
agenda_pricing.refresh_from_db()
|
||||
assert agenda_pricing.min_pricing == 10
|
||||
|
||||
|
||||
def test_detail_agenda_pricing(app, admin_user):
|
||||
pricing = Pricing.objects.create(label='Model')
|
||||
agenda_pricing = AgendaPricing.objects.create(
|
||||
|
|
|
@ -55,6 +55,16 @@ def test_detail_pricing(app, admin_user):
|
|||
resp = app.get('/manage/pricing/model/%s/' % pricing.pk)
|
||||
assert resp.text.count('"/manage/pricing/agenda/%s/"' % agenda.pk) == 1
|
||||
|
||||
assert pricing.reduction_rate == ''
|
||||
assert 'Reduction rate enabled: no' in resp
|
||||
assert 'Reduction rate (template):' not in resp
|
||||
|
||||
pricing.reduction_rate = 'foo'
|
||||
pricing.save()
|
||||
resp = app.get('/manage/pricing/model/%s/' % pricing.pk)
|
||||
assert 'Reduction rate enabled: yes' in resp
|
||||
assert 'Reduction rate (template): <pre>foo</pre>' in resp
|
||||
|
||||
|
||||
def test_edit_pricing(app, admin_user):
|
||||
pricing = Pricing.objects.create(label='Model 1')
|
||||
|
@ -74,6 +84,14 @@ def test_edit_pricing(app, admin_user):
|
|||
pricing.refresh_from_db()
|
||||
assert pricing.label == 'Model Foo'
|
||||
assert pricing.slug == 'foo-bar'
|
||||
assert pricing.reduction_rate == ''
|
||||
|
||||
resp = app.get('/manage/pricing/model/%s/edit/' % pricing.pk)
|
||||
resp.form['reduction_rate'] = 'foo'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/pricing/model/%s/' % pricing.pk)
|
||||
pricing.refresh_from_db()
|
||||
assert pricing.reduction_rate == 'foo'
|
||||
|
||||
|
||||
def test_delete_pricing(app, admin_user):
|
||||
|
|
|
@ -27,6 +27,9 @@ from lingo.pricing.models import (
|
|||
PricingMatrixCell,
|
||||
PricingMatrixRow,
|
||||
PricingMultipleBookingError,
|
||||
PricingReductionRateError,
|
||||
PricingReductionRateFormatError,
|
||||
PricingReductionRateValueError,
|
||||
PricingUnknownCheckStatusError,
|
||||
)
|
||||
|
||||
|
@ -52,8 +55,8 @@ class MockedRequestResponse(mock.Mock):
|
|||
|
||||
def mocked_requests_send(request, **kwargs):
|
||||
data = [
|
||||
{'id': 1, 'fields': {'foo': 'bar', 'bar': False}},
|
||||
{'id': 2, 'fields': {'foo': 'baz', 'bar': True}},
|
||||
{'id': 1, 'fields': {'foo': 'bar', 'bar': False, 'rate': 42}},
|
||||
{'id': 2, 'fields': {'foo': 'baz', 'bar': True, 'rate': 35}},
|
||||
] # fake result
|
||||
return MockedRequestResponse(content=json.dumps({'data': data}))
|
||||
|
||||
|
@ -668,6 +671,184 @@ def test_compute_pricing():
|
|||
)
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_compute_reduction_rate(mock_send, context):
|
||||
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),
|
||||
)
|
||||
|
||||
# empty template
|
||||
assert pricing.reduction_rate == ''
|
||||
with pytest.raises(PricingReductionRateFormatError) as e:
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
assert e.value.details == {'reduction_rate': '', 'wanted': 'decimal'}
|
||||
|
||||
for value in ['{% for %}', '{{ "foo"|add:user.email }}']:
|
||||
pricing.reduction_rate = value
|
||||
pricing.save()
|
||||
with pytest.raises(PricingReductionRateError):
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
|
||||
# not a decimal
|
||||
for value in ['bar', '{{ foo }}']:
|
||||
pricing.reduction_rate = value
|
||||
pricing.save()
|
||||
with pytest.raises(PricingReductionRateFormatError) as e:
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={'foo': 'bar'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
assert e.value.details == {'reduction_rate': '"bar"', 'wanted': 'decimal'}
|
||||
|
||||
# not a good rate
|
||||
for value in ['-0.01', '{{ min }}', '{{ 0|add:-1 }}', '100.01', '{{ max }}', '{{ 100|add:1 }}']:
|
||||
pricing.reduction_rate = value
|
||||
pricing.save()
|
||||
with pytest.raises(PricingReductionRateValueError) as e:
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={'min': '-0.01', 'max': '100.01'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
assert e.value.details == {}
|
||||
|
||||
for value in ['42', '{{ foo }}', '{{ cards|objects:"foo"|first|get:"fields"|get:"rate" }}']:
|
||||
pricing.reduction_rate = value
|
||||
pricing.save()
|
||||
assert (
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={'foo': '42'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
== 42
|
||||
)
|
||||
|
||||
# user_external_id and payer_external_id can be used
|
||||
pricing.reduction_rate = {
|
||||
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_id|filter_by:"bar"|filter_value:payer_external_id|list }}',
|
||||
}
|
||||
pricing.save()
|
||||
mock_send.reset_mock()
|
||||
with pytest.raises(PricingReductionRateFormatError):
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url
|
||||
pricing.reduction_rate = {
|
||||
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_raw_id|filter_by:"bar"|filter_value:payer_external_raw_id|list }}',
|
||||
}
|
||||
pricing.save()
|
||||
mock_send.reset_mock()
|
||||
with pytest.raises(PricingReductionRateFormatError):
|
||||
agenda_pricing.compute_reduction_rate(
|
||||
request=context['request'],
|
||||
original_context={},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
)
|
||||
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
|
||||
def test_apply_reduction_rate(context):
|
||||
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),
|
||||
)
|
||||
|
||||
# empty template
|
||||
assert pricing.reduction_rate == ''
|
||||
assert agenda_pricing.apply_reduction_rate(
|
||||
pricing=42,
|
||||
request=context['request'],
|
||||
context={'foo': '50'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
) == (42, {})
|
||||
|
||||
# template with correct value
|
||||
pricing.reduction_rate = '{{ foo }}'
|
||||
pricing.save()
|
||||
assert agenda_pricing.apply_reduction_rate(
|
||||
pricing=42,
|
||||
request=context['request'],
|
||||
context={'foo': '50'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
) == (
|
||||
21,
|
||||
{
|
||||
'computed_pricing': 42,
|
||||
'reduction_rate': 50,
|
||||
'reduced_pricing': 21,
|
||||
'min_pricing': None,
|
||||
'bounded_pricing': 21,
|
||||
},
|
||||
)
|
||||
|
||||
# with a min value
|
||||
agenda_pricing.min_pricing = 22
|
||||
agenda_pricing.save()
|
||||
assert agenda_pricing.apply_reduction_rate(
|
||||
pricing=42,
|
||||
request=context['request'],
|
||||
context={'foo': '50'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
) == (
|
||||
22,
|
||||
{
|
||||
'computed_pricing': 42,
|
||||
'reduction_rate': 50,
|
||||
'reduced_pricing': 21,
|
||||
'min_pricing': 22,
|
||||
'bounded_pricing': 22,
|
||||
},
|
||||
)
|
||||
agenda_pricing.min_pricing = 21
|
||||
agenda_pricing.save()
|
||||
assert agenda_pricing.apply_reduction_rate(
|
||||
pricing=42,
|
||||
request=context['request'],
|
||||
context={'foo': '50'},
|
||||
user_external_id='child:42',
|
||||
payer_external_id='parent:35',
|
||||
) == (
|
||||
21,
|
||||
{
|
||||
'computed_pricing': 42,
|
||||
'reduction_rate': 50,
|
||||
'reduced_pricing': 21,
|
||||
'min_pricing': 21,
|
||||
'bounded_pricing': 21,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_format_pricing_data():
|
||||
agenda = Agenda.objects.create(label='Foo bar')
|
||||
pricing = Pricing.objects.create(label='Foo bar')
|
||||
|
@ -1039,6 +1220,7 @@ def test_get_pricing_data(context):
|
|||
'calculation_details': {
|
||||
'pricing': 42,
|
||||
'criterias': {'foo': 'bar'},
|
||||
'reduction_rate': {},
|
||||
'context': {'domicile': 'commune', 'qf': '2'},
|
||||
},
|
||||
}
|
||||
|
@ -1078,6 +1260,7 @@ def test_get_pricing_data_for_event(context):
|
|||
'calculation_details': {
|
||||
'pricing': 42,
|
||||
'criterias': {'foo': 'bar'},
|
||||
'reduction_rate': {},
|
||||
'context': {'domicile': 'commune', 'qf': '2'},
|
||||
},
|
||||
'booking_details': {
|
||||
|
@ -1224,12 +1407,17 @@ def test_aggregate_pricing_data(modifier, pricing_amount):
|
|||
agenda_pricing.agendas.add(agenda)
|
||||
|
||||
assert agenda_pricing.aggregate_pricing_data(
|
||||
pricing=42, criterias={'foo': 'bar'}, context={'domicile': 'commune', 'qf': 2}, modifier=modifier
|
||||
pricing=42,
|
||||
criterias={'foo': 'bar'},
|
||||
reduction_rate={'foo': 'baz'},
|
||||
context={'domicile': 'commune', 'qf': 2},
|
||||
modifier=modifier,
|
||||
) == {
|
||||
'pricing': pricing_amount,
|
||||
'calculation_details': {
|
||||
'pricing': 42,
|
||||
'criterias': {'foo': 'bar'},
|
||||
'reduction_rate': {'foo': 'baz'},
|
||||
'context': {'domicile': 'commune', 'qf': 2},
|
||||
},
|
||||
'booking_details': modifier,
|
||||
|
|
Loading…
Reference in New Issue