pricing: format pricing_data to ignore category ordering (#66899)
This commit is contained in:
parent
41eda72b2e
commit
2389a3f706
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue