pricing: iter_pricing_matrix method (#65053)
This commit is contained in:
parent
7f85145858
commit
35600c2952
|
@ -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
|
||||
|
|
|
@ -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()) == []
|
||||
|
|
Loading…
Reference in New Issue