pricing: methods to work with min_pricing_data (#89605)

This commit is contained in:
Lauréline Guérin 2024-04-16 12:00:24 +02:00
parent 424ffd002d
commit 4615f17360
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 296 additions and 184 deletions

View File

@ -759,6 +759,10 @@ class AbstractJournalLine(models.Model):
),
'PricingDataError': _('Impossible to determine a pricing for criterias: %(criterias)s'),
'PricingDataFormatError': _('Pricing is not a %(wanted)s: %(pricing)s'),
'MinPricingDataError': _(
'Impossible to determine a minimal pricing for criterias: %(criterias)s'
),
'MinPricingDataFormatError': _('Minimal pricing is not a %(wanted)s: %(pricing)s'),
'PricingReductionRateError': _('Impossible to determine a reduction rate'),
'PricingReductionRateFormatError': _('Reduction rate is not a %(wanted)s: %(reduction_rate)s'),
'PricingReductionRateValueError': _('Reduction rate bad value: %(reduction_rate)s'),

View File

@ -52,10 +52,18 @@ class PricingDataError(PricingError):
pass
class MinPricingDataError(PricingError):
pass
class PricingDataFormatError(PricingError):
pass
class MinPricingDataFormatError(PricingError):
pass
class PricingReductionRateError(PricingError):
pass
@ -586,7 +594,7 @@ class Pricing(WithApplicationMixin, models.Model):
context['payer_external_raw_id'] = payer_external_id.split(':')[1]
return self.get_extra_variables(request, context)
def format_pricing_data(self):
def format_pricing_data(self, field='pricing'):
# format data to ignore category ordering
def _format(data):
@ -604,13 +612,13 @@ class Pricing(WithApplicationMixin, models.Model):
return {
self.format_pricing_data_key(criterias): pricing
for criterias, pricing in _format(self.pricing_data)
for criterias, pricing in _format(getattr(self, '%s_data' % field))
}
def format_pricing_data_key(self, values):
return '||'.join(sorted(values))
def compute_pricing(self, context):
def _compute_pricing(self, context, field):
criterias = {}
categories = []
# for each category
@ -640,22 +648,32 @@ class Pricing(WithApplicationMixin, models.Model):
criterias[category.slug] = default_criterias[0].slug
# now search for pricing values matching found criterias
pricing_data = self.format_pricing_data()
pricing_data = self.format_pricing_data(field=field)
pricing = pricing_data.get(
self.format_pricing_data_key(['%s:%s' % (k, v) for k, v in criterias.items()])
)
if pricing is None:
if field == 'min_pricing':
raise MinPricingDataError(details={'criterias': criterias})
raise PricingDataError(details={'criterias': criterias})
try:
pricing = decimal.Decimal(pricing)
except (decimal.InvalidOperation, ValueError, TypeError):
if field == 'min_pricing':
raise MinPricingDataFormatError(details={'pricing': pricing, 'wanted': 'decimal'})
raise PricingDataFormatError(details={'pricing': pricing, 'wanted': 'decimal'})
if self.kind == 'effort':
return round(pricing, 4), criterias
return round(pricing, 2), criterias
def compute_pricing(self, context):
return self._compute_pricing(context, 'pricing')
def compute_min_pricing(self, context):
return self._compute_pricing(context, 'min_pricing')
def _compute_template(self, request, original_context, user_external_id, payer_external_id, template):
context = RequestContext(request)
context.push(original_context)
@ -854,9 +872,9 @@ class Pricing(WithApplicationMixin, models.Model):
}
)
def iter_pricing_matrix(self):
def _iter_pricing_matrix(self, field):
categories = self.categories.all().order_by('pricingcriteriacategory__order')[:3]
pricing_data = self.format_pricing_data()
pricing_data = self.format_pricing_data(field)
if not categories:
return
@ -877,6 +895,12 @@ class Pricing(WithApplicationMixin, models.Model):
pricing_data=pricing_data,
)
def iter_pricing_matrix(self):
return self._iter_pricing_matrix('pricing')
def iter_min_pricing_matrix(self):
return self._iter_pricing_matrix('min_pricing')
def get_pricing_matrix(self, main_criteria, categories, pricing_data):
matrix = PricingMatrix(
criteria=main_criteria,

View File

@ -2538,6 +2538,8 @@ def test_journal_pool_lines(app, admin_user, draft):
('MultipleDefaultCriteriaCondition', {'category': 'cat-foo'}),
('PricingDataError', {'criterias': {'qf': 'qf-1', 'foo': 'bar'}}),
('PricingDataFormatError', {'pricing': 'foobar', 'wanted': 'decimal'}),
('MinPricingDataError', {'criterias': {'qf': 'qf-1', 'foo': 'bar'}}),
('MinPricingDataFormatError', {'pricing': 'foobar', 'wanted': 'decimal'}),
('PricingReductionRateError', {}),
('PricingReductionRateFormatError', {'reduction_rate': 'foo', 'wanted': 'decimal'}),
('PricingReductionRateValueError', {'reduction_rate': 42}),
@ -2663,7 +2665,7 @@ def test_journal_pool_lines(app, admin_user, draft):
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk)
)
assert len(resp.pyquery('td.status')) == 30
assert len(resp.pyquery('td.status')) == 32
assert format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[0].pk).text()) == 'Success'
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[0].pk).text().strip()
@ -2716,219 +2718,237 @@ def test_journal_pool_lines(app, admin_user, draft):
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[6].pk).text())
== 'Error (Impossible to determine a reduction rate) ignore - mark as fixed'
== 'Error (Impossible to determine a minimal pricing for criterias: qf-1 (category: qf), bar (category: foo)) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[6].pk).text().strip()
== "{'error': 'PricingReductionRateError', 'error_details': {}} "
== "{'error': 'MinPricingDataError', 'error_details': {'criterias': {'foo': 'bar', 'qf': 'qf-1'}}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[7].pk).text())
== 'Error (Reduction rate is not a decimal: foo) ignore - mark as fixed'
== 'Error (Minimal pricing is not a decimal: foobar) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[7].pk).text().strip()
== "{'error': 'PricingReductionRateFormatError', 'error_details': {'reduction_rate': 'foo', 'wanted': 'decimal'}} "
== "{'error': 'MinPricingDataFormatError', 'error_details': {'pricing': 'foobar', 'wanted': 'decimal'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[8].pk).text())
== 'Error (Reduction rate bad value: 42) ignore - mark as fixed'
== 'Error (Impossible to determine a reduction rate) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[8].pk).text().strip()
== "{'error': 'PricingReductionRateValueError', 'error_details': {'reduction_rate': 42}} "
== "{'error': 'PricingReductionRateError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[9].pk).text())
== 'Error (Impossible to determine an effort rate target) ignore - mark as fixed'
== 'Error (Reduction rate is not a decimal: foo) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[9].pk).text().strip()
== "{'error': 'PricingEffortRateTargetError', 'error_details': {}} "
== "{'error': 'PricingReductionRateFormatError', 'error_details': {'reduction_rate': 'foo', 'wanted': 'decimal'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[10].pk).text())
== 'Error (Effort rate target is not a decimal: foo) ignore - mark as fixed'
== 'Error (Reduction rate bad value: 42) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[10].pk).text().strip()
== "{'error': 'PricingEffortRateTargetFormatError', 'error_details': {'effort_rate_target': 'foo', 'wanted': 'decimal'}} "
== "{'error': 'PricingReductionRateValueError', 'error_details': {'reduction_rate': 42}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[11].pk).text())
== 'Error (Effort rate target bad value: 42) ignore - mark as fixed'
== 'Error (Impossible to determine an effort rate target) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[11].pk).text().strip()
== "{'error': 'PricingEffortRateTargetValueError', 'error_details': {'effort_rate_target': 42}} "
== "{'error': 'PricingEffortRateTargetError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[12].pk).text())
== 'Error (Impossible to determine an accounting code) ignore - mark as fixed'
== 'Error (Effort rate target is not a decimal: foo) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[12].pk).text().strip()
== "{'error': 'PricingAccountingCodeError', 'error_details': {}} "
== "{'error': 'PricingEffortRateTargetFormatError', 'error_details': {'effort_rate_target': 'foo', 'wanted': 'decimal'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[13].pk).text())
== 'Error (Unknown check status: unknown) ignore - mark as fixed'
== 'Error (Effort rate target bad value: 42) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[13].pk).text().strip()
== "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}} "
== "{'error': 'PricingEffortRateTargetValueError', 'error_details': {'effort_rate_target': 42}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[14].pk).text())
== 'Error (Event is not checked) ignore - mark as fixed'
== 'Error (Impossible to determine an accounting code) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[14].pk).text().strip()
== "{'error': 'PricingEventNotCheckedError', 'error_details': {}} "
== "{'error': 'PricingAccountingCodeError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[15].pk).text())
== 'Error (Booking is not checked) ignore - mark as fixed'
== 'Error (Unknown check status: unknown) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[15].pk).text().strip()
== "{'error': 'PricingBookingNotCheckedError', 'error_details': {}} "
== "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[16].pk).text())
== 'Error (Multiple booking found) ignore - mark as fixed'
== 'Error (Event is not checked) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[16].pk).text().strip()
== "{'error': 'PricingMultipleBookingError', 'error_details': {}} "
== "{'error': 'PricingEventNotCheckedError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[17].pk).text())
== 'Error (Check type error: not found) ignore - mark as fixed'
== 'Error (Booking is not checked) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[17].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}} "
== "{'error': 'PricingBookingNotCheckedError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[18].pk).text())
== 'Error (Check type error: pricing not configured (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
== 'Error (Multiple booking found) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[18].pk).text().strip()
== "{'error': 'PricingMultipleBookingError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[19].pk).text())
== 'Error (Check type error: not found) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[19].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[20].pk).text())
== 'Error (Check type error: pricing not configured (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[20].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', "
"'check_type_group': 'foo-bar', 'reason': 'not-configured'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[19].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[21].pk).text())
== 'Error (Check type error: wrong kind (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[19].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[21].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', "
"'check_type_group': 'foo-bar', 'reason': 'wrong-kind'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[20].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[22].pk).text())
== 'Error (Impossible to determine payer: template is empty) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[20].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[22].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'empty-template'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[21].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[23].pk).text())
== 'Error (Impossible to determine payer: result is empty) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[21].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[23].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'empty-result'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[22].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[24].pk).text())
== 'Error (Impossible to determine payer: syntax error) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[22].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[24].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'syntax-error'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[23].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[25].pk).text())
== 'Error (Impossible to determine payer: variable error) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[23].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[25].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'variable-error'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[24].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[26].pk).text())
== 'Error (Impossible to determine payer: card model is not configured) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[24].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[26].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'missing-card-model'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[25].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[27].pk).text())
== 'Error (Impossible to determine payer: payer is not configured on regie) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[25].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[27].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'missing-payer'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[26].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[28].pk).text())
== 'Error (Impossible to get payer foo: mapping not defined) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[26].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[28].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'not-defined'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[27].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[29].pk).text())
== 'Fixed (Impossible to get payer foo: result is empty) reset'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[27].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[29].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'empty-result'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[28].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[30].pk).text())
== 'Ignored (Impossible to get payer foo: result is not a boolean) reset'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[28].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[30].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'not-a-boolean'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[29].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[31].pk).text())
== 'Success (Injected)'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[29].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[31].pk).text().strip()
== "{'foo': 'bar'} {} {}"
)
@ -2964,12 +2984,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_external_id': 'payer:2'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 31
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_first_name': 'first'},
)
assert len(resp.pyquery('tr td.status')) == 30
assert len(resp.pyquery('tr td.status')) == 32
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_first_name': 'first1'},
@ -2979,7 +2999,7 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_last_name': 'last'},
)
assert len(resp.pyquery('tr td.status')) == 30
assert len(resp.pyquery('tr td.status')) == 32
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_last_name': 'last1'},
@ -2994,12 +3014,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_direct_debit': True},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 31
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_external_id': 'user:1'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 31
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_external_id': 'user:2'},
@ -3009,27 +3029,27 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_first_name': 'first'},
)
assert len(resp.pyquery('tr td.status')) == 30
assert len(resp.pyquery('tr td.status')) == 32
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_first_name': 'first1'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 31
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_last_name': 'last'},
)
assert len(resp.pyquery('tr td.status')) == 30
assert len(resp.pyquery('tr td.status')) == 32
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_last_name': 'last1'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 31
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'agenda': 'agenda-a'},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 30
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'agenda': 'agenda-b'},
@ -3044,7 +3064,7 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'event': 'agenda-a@event-aa'},
)
assert len(resp.pyquery('tr td.status')) == 27
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'event': 'agenda-b@event-b'},
@ -3079,12 +3099,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error'},
)
assert len(resp.pyquery('tr td.status')) == 27
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error_todo'},
)
assert len(resp.pyquery('tr td.status')) == 25
assert len(resp.pyquery('tr td.status')) == 27
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error_ignored'},

View File

@ -14,6 +14,8 @@ from lingo.pricing.models import (
Criteria,
CriteriaCategory,
CriteriaConditionNotFound,
MinPricingDataError,
MinPricingDataFormatError,
MultipleDefaultCriteriaCondition,
Pricing,
PricingAccountingCodeError,
@ -512,7 +514,8 @@ def test_compute_condition(condition, context, result):
assert criteria.compute_condition(context) == result
def test_compute_pricing():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_compute_pricing(field):
agenda = Agenda.objects.create(label='Foo bar')
category = CriteriaCategory.objects.create(label='QF', slug='qf')
pricing = Pricing.objects.create(
@ -521,9 +524,10 @@ def test_compute_pricing():
)
pricing.categories.add(category, through_defaults={'order': 1})
pricing.agendas.add(agenda)
compute_method = getattr(pricing, 'compute_%s' % field)
# no criteria defined on pricing
with pytest.raises(CriteriaConditionNotFound) as e:
pricing.compute_pricing(context={'qf': 2})
compute_method(context={'qf': 2})
assert e.value.details == {'category': 'qf'}
# conditions are not set
@ -532,7 +536,7 @@ def test_compute_pricing():
pricing.criterias.add(criteria1)
pricing.criterias.add(criteria2)
with pytest.raises(CriteriaConditionNotFound) as e:
pricing.compute_pricing(context={'qf': 2})
compute_method(context={'qf': 2})
assert e.value.details == {'category': 'qf'}
# conditions set, but no match
@ -541,16 +545,19 @@ def test_compute_pricing():
criteria2.condition = 'False'
criteria2.save()
with pytest.raises(CriteriaConditionNotFound) as e:
pricing.compute_pricing(context={'qf': 2})
compute_method(context={'qf': 2})
assert e.value.details == {'category': 'qf'}
# but with a default criteria, there is a match, but pricing.pricing_data is not defined
# but with a default criteria, there is a match, but pricing.(min_)pricing_data is not defined
default_criteria1 = Criteria.objects.create(
label='Else 1', slug='else-1', category=category, default=True
)
pricing.criterias.add(default_criteria1)
with pytest.raises(PricingDataError) as e:
pricing.compute_pricing(context={'qf': 2})
error_class = PricingDataError
if field == 'min_pricing':
error_class = MinPricingDataError
with pytest.raises(error_class) as e:
compute_method(context={'qf': 2})
assert e.value.details == {'criterias': {'qf': 'else-1'}}
# with more than one default criteria, fail
default_criteria2 = Criteria.objects.create(
@ -558,55 +565,83 @@ def test_compute_pricing():
)
pricing.criterias.add(default_criteria2)
with pytest.raises(MultipleDefaultCriteriaCondition) as e:
pricing.compute_pricing(context={'qf': 2})
compute_method(context={'qf': 2})
assert e.value.details == {'category': 'qf'}
Criteria.objects.filter(default=True).delete() # remove default criterias
# criteria found, but pricing.pricing_data is not defined
# criteria found, but pricing.(min_)pricing_data is not defined
criteria1.condition = 'qf < 1'
criteria1.save()
criteria2.condition = 'qf >= 1'
criteria2.save()
with pytest.raises(PricingDataError) as e:
pricing.compute_pricing(context={'qf': 2})
error_class = PricingDataError
if field == 'min_pricing':
error_class = MinPricingDataError
with pytest.raises(error_class) as e:
compute_method(context={'qf': 2})
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# criteria not found in pricing_data
pricing.pricing_data = {
'qf:qf-0': 42,
}
# criteria not found in (min_)pricing_data
setattr(
pricing,
'%s_data' % field,
{
'qf:qf-0': 42,
},
)
pricing.save()
with pytest.raises(PricingDataError) as e:
pricing.compute_pricing(context={'qf': 2})
error_class = PricingDataError
if field == 'min_pricing':
error_class = MinPricingDataError
with pytest.raises(error_class) as e:
compute_method(context={'qf': 2})
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# criteria found, but value is wrong
for value in ['foo', ['foo']]:
pricing.pricing_data = {
'qf:qf-0': 42,
'qf:qf-1': value,
}
setattr(
pricing,
'%s_data' % field,
{
'qf:qf-0': 42,
'qf:qf-1': value,
},
)
pricing.save()
with pytest.raises(PricingDataFormatError) as e:
pricing.compute_pricing(context={'qf': 2})
error_class = PricingDataFormatError
if field == 'min_pricing':
error_class = MinPricingDataFormatError
with pytest.raises(error_class) as e:
compute_method(context={'qf': 2})
assert e.value.details == {'pricing': value, 'wanted': 'decimal'}
for value in [[], {}]:
pricing.pricing_data = {
'qf:qf-0': 42,
'qf:qf-1': value,
}
setattr(
pricing,
'%s_data' % field,
{
'qf:qf-0': 42,
'qf:qf-1': value,
},
)
pricing.save()
with pytest.raises(PricingDataError) as e:
pricing.compute_pricing(context={'qf': 2})
error_class = PricingDataError
if field == 'min_pricing':
error_class = MinPricingDataError
with pytest.raises(error_class) as e:
compute_method(context={'qf': 2})
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# correct value (decimal)
pricing.pricing_data = {
'qf:qf-0': 42,
'qf:qf-1': 52,
}
setattr(
pricing,
'%s_data' % field,
{
'qf:qf-0': 42,
'qf:qf-1': 52,
},
)
pricing.save()
assert pricing.compute_pricing(context={'qf': 2}) == (52, {'qf': 'qf-1'})
assert compute_method(context={'qf': 2}) == (52, {'qf': 'qf-1'})
# more complexe pricing model
category2 = CriteriaCategory.objects.create(label='Domicile', slug='domicile')
@ -621,30 +656,34 @@ def test_compute_pricing():
pricing.criterias.add(criteria2)
# correct definition
pricing.pricing_data = {
'domicile:dom-0': {
'qf:qf-0': 3,
'qf:qf-1': 5,
setattr(
pricing,
'%s_data' % field,
{
'domicile:dom-0': {
'qf:qf-0': 3,
'qf:qf-1': 5,
},
'domicile:else': {
'qf:qf-0': 7,
'qf:qf-1': 10,
},
},
'domicile:else': {
'qf:qf-0': 7,
'qf:qf-1': 10,
},
}
)
pricing.save()
assert pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) == (
assert compute_method(context={'qf': 2, 'domicile': 'commune'}) == (
5,
{'domicile': 'dom-0', 'qf': 'qf-1'},
)
assert pricing.compute_pricing(context={'qf': 0, 'domicile': 'commune'}) == (
assert compute_method(context={'qf': 0, 'domicile': 'commune'}) == (
3,
{'domicile': 'dom-0', 'qf': 'qf-0'},
)
assert pricing.compute_pricing(context={'qf': 2, 'domicile': 'ext'}) == (
assert compute_method(context={'qf': 2, 'domicile': 'ext'}) == (
10,
{'domicile': 'else', 'qf': 'qf-1'},
)
assert pricing.compute_pricing(context={'qf': 0, 'domicile': 'ext'}) == (
assert compute_method(context={'qf': 0, 'domicile': 'ext'}) == (
7,
{'domicile': 'else', 'qf': 'qf-0'},
)
@ -652,19 +691,19 @@ def test_compute_pricing():
# category ordering doesn't matter
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category).update(order=2)
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category2).update(order=1)
assert pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) == (
assert compute_method(context={'qf': 2, 'domicile': 'commune'}) == (
5,
{'domicile': 'dom-0', 'qf': 'qf-1'},
)
assert pricing.compute_pricing(context={'qf': 0, 'domicile': 'commune'}) == (
assert compute_method(context={'qf': 0, 'domicile': 'commune'}) == (
3,
{'domicile': 'dom-0', 'qf': 'qf-0'},
)
assert pricing.compute_pricing(context={'qf': 2, 'domicile': 'ext'}) == (
assert compute_method(context={'qf': 2, 'domicile': 'ext'}) == (
10,
{'domicile': 'else', 'qf': 'qf-1'},
)
assert pricing.compute_pricing(context={'qf': 0, 'domicile': 'ext'}) == (
assert compute_method(context={'qf': 0, 'domicile': 'ext'}) == (
7,
{'domicile': 'else', 'qf': 'qf-0'},
)
@ -1184,34 +1223,39 @@ def test_compute_accounting_code(mock_send, context, nocache):
assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
def test_format_pricing_data():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_format_pricing_data(field):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
pricing.agendas.add(agenda)
assert pricing.format_pricing_data() == {}
assert pricing.format_pricing_data(field=field) == {}
pricing.pricing_data = {
'cat-1:crit-1-1': {
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 111,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 114,
setattr(
pricing,
'%s_data' % field,
{
'cat-1:crit-1-1': {
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 111,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 114,
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 132,
},
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 132,
'cat-1:crit-1-2': {
'cat-2:crit-2-2': {
'cat-3:crit-3-3': 223,
},
},
},
'cat-1:crit-1-2': {
'cat-2:crit-2-2': {
'cat-3:crit-3-3': 223,
},
},
}
)
pricing.save()
assert pricing.format_pricing_data() == {
assert pricing.format_pricing_data(field=field) == {
'cat-1:crit-1-1||cat-2:crit-2-1||cat-3:crit-3-1': 111,
'cat-1:crit-1-1||cat-2:crit-2-1||cat-3:crit-3-3': 'not-a-decimal',
'cat-1:crit-1-1||cat-2:crit-2-1||cat-3:crit-3-4': 114,
@ -1219,23 +1263,23 @@ def test_format_pricing_data():
'cat-1:crit-1-2||cat-2:crit-2-2||cat-3:crit-3-3': 223,
}
pricing.pricing_data = {'foo': 42}
setattr(pricing, '%s_data' % field, {'foo': 42})
pricing.save()
assert pricing.format_pricing_data() == {'foo': 42}
assert pricing.format_pricing_data(field=field) == {'foo': 42}
# wrong data
pricing.pricing_data = 'foo'
setattr(pricing, '%s_data' % field, 'foo')
pricing.save()
assert pricing.format_pricing_data() == {'': 'foo'}
pricing.pricing_data = []
assert pricing.format_pricing_data(field=field) == {'': 'foo'}
setattr(pricing, '%s_data' % field, [])
pricing.save()
assert pricing.format_pricing_data() == {}
pricing.pricing_data = ['foo']
assert pricing.format_pricing_data(field=field) == {}
setattr(pricing, '%s_data' % field, ['foo'])
pricing.save()
assert pricing.format_pricing_data() == {'': ['foo']}
pricing.pricing_data = {'foo': []}
assert pricing.format_pricing_data(field=field) == {'': ['foo']}
setattr(pricing, '%s_data' % field, {'foo': []})
pricing.save()
assert pricing.format_pricing_data() == {}
assert pricing.format_pricing_data(field=field) == {}
def test_get_booking_modifier_unknown_check_status():
@ -1740,7 +1784,8 @@ def test_aggregate_pricing_data(modifier, pricing_amount):
}
def test_pricing_iter_pricing_matrix_3_categories():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_pricing_iter_pricing_matrix_3_categories(field):
category1 = CriteriaCategory.objects.create(label='Cat 1')
criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1)
criteria12 = Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2)
@ -1761,13 +1806,14 @@ def test_pricing_iter_pricing_matrix_3_categories():
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
iter_method = getattr(pricing, 'iter_%s_matrix' % field)
pricing.categories.add(category1, through_defaults={'order': 1})
pricing.categories.add(category2, through_defaults={'order': 2})
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.categories.add(category4, through_defaults={'order': 4})
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk))
pricing.agendas.add(agenda)
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=criteria11,
rows=[
@ -1845,28 +1891,32 @@ def test_pricing_iter_pricing_matrix_3_categories():
]
# some data defined
pricing.pricing_data = {
'cat-1:crit-1-1': {
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 111,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 114,
setattr(
pricing,
'%s_data' % field,
{
'cat-1:crit-1-1': {
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 111,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 114,
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 132,
},
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 132,
'cat-1:crit-1-2': {
'cat-2:crit-2-2': {
'cat-3:crit-3-3': 223,
},
},
},
'cat-1:crit-1-2': {
'cat-2:crit-2-2': {
'cat-3:crit-3-3': 223,
},
},
}
)
pricing.save()
# category ordering doesn't matter
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category1).update(order=2)
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category2).update(order=1)
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=criteria21,
rows=[
@ -1969,7 +2019,8 @@ def test_pricing_iter_pricing_matrix_3_categories():
]
def test_pricing_iter_pricing_matrix_2_categories():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_pricing_iter_pricing_matrix_2_categories(field):
category2 = CriteriaCategory.objects.create(label='Cat 2')
criteria21 = Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1)
criteria22 = Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2)
@ -1986,12 +2037,13 @@ def test_pricing_iter_pricing_matrix_2_categories():
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
iter_method = getattr(pricing, 'iter_%s_matrix' % field)
pricing.categories.add(category2, through_defaults={'order': 2})
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk))
pricing.agendas.add(agenda)
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=None,
rows=[
@ -2032,18 +2084,22 @@ def test_pricing_iter_pricing_matrix_2_categories():
]
# some data defined
pricing.pricing_data = {
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 11,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 14,
setattr(
pricing,
'%s_data' % field,
{
'cat-2:crit-2-1': {
'cat-3:crit-3-1': 11,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 14,
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 32,
},
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 32,
},
}
)
pricing.save()
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=None,
rows=[
@ -2084,7 +2140,8 @@ def test_pricing_iter_pricing_matrix_2_categories():
]
def test_pricing_iter_pricing_matrix_1_category():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_pricing_iter_pricing_matrix_1_category(field):
category3 = CriteriaCategory.objects.create(label='Cat 3')
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1)
criteria33 = Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3)
@ -2097,11 +2154,12 @@ def test_pricing_iter_pricing_matrix_1_category():
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
iter_method = getattr(pricing, 'iter_%s_matrix' % field)
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk))
pricing.agendas.add(agenda)
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=None,
rows=[
@ -2134,13 +2192,17 @@ def test_pricing_iter_pricing_matrix_1_category():
]
# some data defined
pricing.pricing_data = {
'cat-3:crit-3-1': 1,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 4,
}
setattr(
pricing,
'%s_data' % field,
{
'cat-3:crit-3-1': 1,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 4,
},
)
pricing.save()
assert list(pricing.iter_pricing_matrix()) == [
assert list(iter_method()) == [
PricingMatrix(
criteria=None,
rows=[
@ -2173,12 +2235,14 @@ def test_pricing_iter_pricing_matrix_1_category():
]
def test_pricing_iter_pricing_matrix_empty():
@pytest.mark.parametrize('field', ['pricing', 'min_pricing'])
def test_pricing_iter_pricing_matrix_empty(field):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
iter_method = getattr(pricing, 'iter_%s_matrix' % field)
pricing.agendas.add(agenda)
assert list(pricing.iter_pricing_matrix()) == []
assert list(iter_method()) == []