pricing: iter_pricing_matrix method (#65053)

This commit is contained in:
Lauréline Guérin 2022-05-12 14:24:36 +02:00
parent 7f85145858
commit 35600c2952
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
2 changed files with 518 additions and 0 deletions

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import dataclasses
import decimal
from django.contrib.postgres.fields import JSONField
@ -156,6 +157,10 @@ class Criteria(models.Model):
'order': self.order,
}
@property
def identifier(self):
return '%s:%s' % (self.category.slug, self.slug)
def compute_condition(self, context):
try:
template = Template('{%% if %s %%}OK{%% endif %%}' % self.condition)
@ -276,6 +281,24 @@ class PricingCriteriaCategory(models.Model):
}
@dataclasses.dataclass
class PricingMatrixCell:
criteria: Criteria
value: decimal.Decimal
@dataclasses.dataclass
class PricingMatrixRow:
criteria: Criteria
cells: list[PricingMatrixCell]
@dataclasses.dataclass
class PricingMatrix:
criteria: Criteria
rows: list[PricingMatrixRow]
class AgendaPricing(models.Model):
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE)
@ -453,3 +476,71 @@ class AgendaPricing(models.Model):
'reason': 'not-configured',
}
)
def iter_pricing_matrix(self):
categories = self.pricing.categories.all().order_by('pricingcriteriacategory__order')[:3]
pricing_data = self.pricing_data or {}
if not categories:
return
if len(categories) < 3:
yield self.get_pricing_matrix(
main_criteria=None, categories=categories, pricing_data=pricing_data
)
return
# criterias are ordered
for criteria in self.pricing.criterias.all():
if criteria.category != categories[0]:
continue
yield self.get_pricing_matrix(
main_criteria=criteria,
categories=categories[1:],
pricing_data=pricing_data.get(criteria.identifier) or {},
)
def get_pricing_matrix(self, main_criteria, categories, pricing_data):
matrix = PricingMatrix(
criteria=main_criteria,
rows=[],
)
def get_pricing_matrix_cell(criteria_2, criteria_3, _pricing_data):
try:
value = decimal.Decimal(str(_pricing_data.get(criteria_3.identifier)))
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
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
return matrix

View File

@ -22,6 +22,9 @@ from chrono.pricing.models import (
PricingDataError,
PricingDataFormatError,
PricingEventNotCheckedError,
PricingMatrix,
PricingMatrixCell,
PricingMatrixRow,
PricingMultipleBookingError,
PricingSubscriptionError,
)
@ -997,3 +1000,427 @@ def test_aggregate_pricing_data(modifier, pricing_amount):
},
'booking_details': modifier,
}
def test_agenda_pricing_iter_pricing_matrix_3_categories():
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)
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)
criteria23 = Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3)
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)
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4)
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2)
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5)
category4 = CriteriaCategory.objects.create(label='Cat 4') # ignored
agenda = Agenda.objects.create(label='Foo bar', kind='events')
pricing = Pricing.objects.create(label='Foo bar')
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))
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 list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=criteria11,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
PricingMatrix(
criteria=criteria12,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
]
# some data defined
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 list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=criteria11,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=111),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=132),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=114),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
PricingMatrix(
criteria=criteria12,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=223),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
]
def test_agenda_pricing_iter_pricing_matrix_2_categories():
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)
criteria23 = Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3)
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)
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4)
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2)
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5)
agenda = Agenda.objects.create(label='Foo bar', kind='events')
pricing = Pricing.objects.create(label='Foo bar')
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))
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 list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=None,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
]
# some data defined
agenda_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,
},
'cat-2:crit-2-3': {
'cat-3:crit-3-2': 32,
},
}
agenda_pricing.save()
assert list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=None,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=criteria21, value=11),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=32),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=criteria21, value=None),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=criteria21, value=14),
PricingMatrixCell(criteria=criteria22, value=None),
PricingMatrixCell(criteria=criteria23, value=None),
],
),
],
),
]
def test_agenda_pricing_iter_pricing_matrix_1_category():
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)
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4)
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2)
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5)
agenda = Agenda.objects.create(label='Foo bar', kind='events')
pricing = Pricing.objects.create(label='Foo bar')
pricing.categories.add(category3, through_defaults={'order': 3})
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk))
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 list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=None,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
],
),
]
# some data defined
agenda_pricing.pricing_data = {
'cat-3:crit-3-1': 1,
'cat-3:crit-3-3': 'not-a-decimal',
'cat-3:crit-3-4': 4,
}
agenda_pricing.save()
assert list(agenda_pricing.iter_pricing_matrix()) == [
PricingMatrix(
criteria=None,
rows=[
PricingMatrixRow(
criteria=criteria31,
cells=[
PricingMatrixCell(criteria=None, value=1),
],
),
PricingMatrixRow(
criteria=criteria32,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
PricingMatrixRow(
criteria=criteria33,
cells=[
PricingMatrixCell(criteria=None, value=None),
],
),
PricingMatrixRow(
criteria=criteria34,
cells=[
PricingMatrixCell(criteria=None, value=4),
],
),
],
),
]
def test_agenda_pricing_iter_pricing_matrix_empty():
agenda = Agenda.objects.create(label='Foo bar', kind='events')
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 list(agenda_pricing.iter_pricing_matrix()) == []