pricing: format pricing_data to ignore category ordering (#66899)

This commit is contained in:
Lauréline Guérin 2022-07-06 15:12:34 +02:00
parent 41eda72b2e
commit 2389a3f706
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 201 additions and 82 deletions

View File

@ -425,6 +425,30 @@ class AgendaPricing(models.Model):
context['adult_external_raw_id'] = adult_external_id.split(':')[1]
return self.pricing.get_extra_variables(request, context)
def format_pricing_data(self):
# format data to ignore category ordering
def _format(data):
if not data:
return
if not isinstance(data, dict):
yield [], data
return
for criteria, val in data.items():
result = list(_format(val))
for criterias, pricing in result:
yield [criteria] + criterias, pricing
return {
self.format_pricing_data_key(criterias): pricing
for criterias, pricing in _format(self.pricing_data)
}
def format_pricing_data_key(self, values):
return '||'.join(sorted(values))
def compute_pricing(self, context):
criterias = {}
categories = []
@ -449,23 +473,17 @@ class AgendaPricing(models.Model):
criterias[category.slug] = default_criterias[0].slug
# now search for pricing values matching found criterias
pricing_data = self.pricing_data
# for each category (ordered)
for category in categories:
criteria = criterias[category]
if not isinstance(pricing_data, dict):
raise PricingDataFormatError(
details={'category': category, 'pricing': pricing_data, 'wanted': 'dict'}
)
key = '%s:%s' % (category, criteria)
if key not in pricing_data:
raise PricingDataError(details={'category': category, 'criteria': criteria})
pricing_data = pricing_data[key]
pricing_data = self.format_pricing_data()
pricing = pricing_data.get(
self.format_pricing_data_key(['%s:%s' % (k, v) for k, v in criterias.items()])
)
if pricing is None:
raise PricingDataError(details={'criterias': criterias})
try:
pricing = decimal.Decimal(pricing_data)
pricing = decimal.Decimal(pricing)
except (decimal.InvalidOperation, ValueError, TypeError):
raise PricingDataFormatError(details={'pricing': pricing_data, 'wanted': 'decimal'})
raise PricingDataFormatError(details={'pricing': pricing, 'wanted': 'decimal'})
return pricing, criterias
@ -560,7 +578,7 @@ class AgendaPricing(models.Model):
def iter_pricing_matrix(self):
categories = self.pricing.categories.all().order_by('pricingcriteriacategory__order')[:3]
pricing_data = self.pricing_data or {}
pricing_data = self.format_pricing_data()
if not categories:
return
@ -578,7 +596,7 @@ class AgendaPricing(models.Model):
yield self.get_pricing_matrix(
main_criteria=criteria,
categories=categories[1:],
pricing_data=pricing_data.get(criteria.identifier) or {},
pricing_data=pricing_data,
)
def get_pricing_matrix(self, main_criteria, categories, pricing_data):
@ -587,41 +605,35 @@ class AgendaPricing(models.Model):
rows=[],
)
def get_pricing_matrix_cell(criteria_2, criteria_3, _pricing_data):
def get_pricing_matrix_cell(criteria_2, criteria_3):
criterias = [main_criteria, criteria_2, criteria_3]
key = self.format_pricing_data_key([c.identifier for c in criterias if c])
try:
value = decimal.Decimal(str(_pricing_data.get(criteria_3.identifier)))
value = decimal.Decimal(str(pricing_data.get(key)))
except (decimal.InvalidOperation, ValueError, TypeError):
value = None
return PricingMatrixCell(criteria=criteria_2, value=value)
if len(categories) < 2:
rows = [
PricingMatrixRow(
criteria=criteria,
cells=[get_pricing_matrix_cell(None, criteria, pricing_data)],
)
for criteria in self.pricing.criterias.all()
if criteria.category == categories[0]
]
matrix.rows = rows
criterias_2 = [None]
criterias_3 = [c for c in self.pricing.criterias.all() if c.category == categories[0]]
else:
criterias_2 = [c for c in self.pricing.criterias.all() if c.category == categories[0]]
criterias_3 = [c for c in self.pricing.criterias.all() if c.category == categories[1]]
rows = [
PricingMatrixRow(
criteria=criteria_3,
cells=[
get_pricing_matrix_cell(
criteria_2,
criteria_3,
pricing_data.get(criteria_2.identifier) or {},
)
for criteria_2 in criterias_2
],
)
for criteria_3 in criterias_3
]
matrix.rows = rows
rows = [
PricingMatrixRow(
criteria=criteria_3,
cells=[
get_pricing_matrix_cell(
criteria_2,
criteria_3,
)
for criteria_2 in criterias_2
],
)
for criteria_3 in criterias_3
]
matrix.rows = rows
return matrix

View File

@ -333,8 +333,8 @@ def test_detail_agenda_pricing_2_categories(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
pricing.categories.add(category2, through_defaults={'order': 2})
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.categories.add(category2, through_defaults={'order': 1})
pricing.categories.add(category3, through_defaults={'order': 2})
pricing.criterias.set(Criteria.objects.all())
agenda_pricing = AgendaPricing.objects.create(
agenda=agenda,
@ -815,8 +815,8 @@ def test_edit_agenda_pricing_matrix_2_categories(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
pricing.categories.add(category2, through_defaults={'order': 2})
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.categories.add(category2, through_defaults={'order': 1})
pricing.categories.add(category3, through_defaults={'order': 2})
pricing.criterias.set(Criteria.objects.all())
agenda_pricing = AgendaPricing.objects.create(
agenda=agenda,

View File

@ -424,9 +424,9 @@ def test_compute_pricing():
label='Else 1', slug='else-1', category=category, default=True
)
pricing.criterias.add(default_criteria1)
with pytest.raises(PricingDataFormatError) as e:
with pytest.raises(PricingDataError) as e:
agenda_pricing.compute_pricing(context={'qf': 2})
assert e.value.details == {'category': 'qf', 'pricing': None, 'wanted': 'dict'}
assert e.value.details == {'criterias': {'qf': 'else-1'}}
# with more than one default criteria, fail
default_criteria2 = Criteria.objects.create(
label='Else 2', slug='else-2', category=category, default=True
@ -442,9 +442,9 @@ def test_compute_pricing():
criteria1.save()
criteria2.condition = 'qf >= 1'
criteria2.save()
with pytest.raises(PricingDataFormatError) as e:
with pytest.raises(PricingDataError) as e:
agenda_pricing.compute_pricing(context={'qf': 2})
assert e.value.details == {'category': 'qf', 'pricing': None, 'wanted': 'dict'}
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# criteria not found in pricing_data
agenda_pricing.pricing_data = {
@ -453,10 +453,10 @@ def test_compute_pricing():
agenda_pricing.save()
with pytest.raises(PricingDataError) as e:
agenda_pricing.compute_pricing(context={'qf': 2})
assert e.value.details == {'category': 'qf', 'criteria': 'qf-1'}
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# criteria found, but value is wrong
for value in ['foo', [], {}]:
for value in ['foo', ['foo']]:
agenda_pricing.pricing_data = {
'qf:qf-0': 42,
'qf:qf-1': value,
@ -465,6 +465,15 @@ def test_compute_pricing():
with pytest.raises(PricingDataFormatError) as e:
agenda_pricing.compute_pricing(context={'qf': 2})
assert e.value.details == {'pricing': value, 'wanted': 'decimal'}
for value in [[], {}]:
agenda_pricing.pricing_data = {
'qf:qf-0': 42,
'qf:qf-1': value,
}
agenda_pricing.save()
with pytest.raises(PricingDataError) as e:
agenda_pricing.compute_pricing(context={'qf': 2})
assert e.value.details == {'criterias': {'qf': 'qf-1'}}
# correct value (decimal)
agenda_pricing.pricing_data = {
@ -486,7 +495,7 @@ def test_compute_pricing():
pricing.criterias.add(criteria1)
pricing.criterias.add(criteria2)
# wrong definition
# correct definition
agenda_pricing.pricing_data = {
'domicile:dom-0': {
'qf:qf-0': 3,
@ -498,10 +507,24 @@ def test_compute_pricing():
},
}
agenda_pricing.save()
with pytest.raises(PricingDataError) as e:
agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'})
assert agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) == (
5,
{'domicile': 'dom-0', 'qf': 'qf-1'},
)
assert agenda_pricing.compute_pricing(context={'qf': 0, 'domicile': 'commune'}) == (
3,
{'domicile': 'dom-0', 'qf': 'qf-0'},
)
assert agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'ext'}) == (
10,
{'domicile': 'else', 'qf': 'qf-1'},
)
assert agenda_pricing.compute_pricing(context={'qf': 0, 'domicile': 'ext'}) == (
7,
{'domicile': 'else', 'qf': 'qf-0'},
)
# reorder categories, so the definition is correct
# 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 agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) == (
@ -522,6 +545,62 @@ def test_compute_pricing():
)
def test_format_pricing_data():
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
agenda=agenda,
pricing=pricing,
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
assert agenda_pricing.format_pricing_data() == {}
agenda_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,
},
'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,
},
},
}
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {
'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,
'cat-1:crit-1-1||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,
}
agenda_pricing.pricing_data = {'foo': 42}
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {'foo': 42}
# wrong data
agenda_pricing.pricing_data = 'foo'
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {'': 'foo'}
agenda_pricing.pricing_data = []
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {}
agenda_pricing.pricing_data = ['foo']
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {'': ['foo']}
agenda_pricing.pricing_data = {'foo': []}
agenda_pricing.save()
assert agenda_pricing.format_pricing_data() == {}
def test_get_booking_modifier_unknown_check_status():
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(label='Foo bar')
@ -1131,77 +1210,105 @@ def test_agenda_pricing_iter_pricing_matrix_3_categories():
},
}
agenda_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(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=criteria11,
criteria=criteria21,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=111),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=111),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=132),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=114),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=114),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
],
),
PricingMatrix(
criteria=criteria12,
criteria=criteria22,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=223),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=223),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
],
),
PricingMatrix(
criteria=criteria23,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria11, value=132),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria11, value=None),
PricingMatrixCell(criteria=criteria12, value=None),
],
),
],