add checks on ProductForm

This commit is contained in:
imperosol
2025-11-26 17:08:18 +01:00
parent a354f33ed9
commit b0659524ad
2 changed files with 52 additions and 5 deletions

View File

@@ -5,7 +5,7 @@ from datetime import date, datetime, timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator
from django.db.models import Exists, OuterRef, Q from django.db.models import Exists, OuterRef, Q
from django.forms import BaseModelFormSet from django.forms import BaseModelFormSet
from django.utils.timezone import now from django.utils.timezone import now
@@ -318,7 +318,6 @@ class ProductForm(forms.ModelForm):
} }
counters = forms.ModelMultipleChoiceField( counters = forms.ModelMultipleChoiceField(
help_text=None,
label=_("Counters"), label=_("Counters"),
required=False, required=False,
widget=AutoCompleteSelectMultipleCounter, widget=AutoCompleteSelectMultipleCounter,
@@ -329,10 +328,31 @@ class ProductForm(forms.ModelForm):
super().__init__(*args, instance=instance, **kwargs) super().__init__(*args, instance=instance, **kwargs)
if self.instance.id: if self.instance.id:
self.fields["counters"].initial = self.instance.counters.all() self.fields["counters"].initial = self.instance.counters.all()
if hasattr(self.instance, "formula"):
self.formula_init(self.instance.formula)
self.action_formset = ScheduledProductActionFormSet( self.action_formset = ScheduledProductActionFormSet(
*args, product=self.instance, **kwargs *args, product=self.instance, **kwargs
) )
def formula_init(self, formula: ProductFormula):
"""Part of the form initialisation specific to formula products."""
self.fields["selling_price"].help_text = _(
"This product is a formula. "
"Its price cannot be greater than the price "
"of the products constituting it, which is %(price)s"
) % {"price": formula.max_selling_price}
self.fields["special_selling_price"].help_text = _(
"This product is a formula. "
"Its special price cannot be greater than the price "
"of the products constituting it, which is %(price)s"
) % {"price": formula.max_special_selling_price}
for key, price in (
("selling_price", formula.max_selling_price),
("special_selling_price", formula.max_special_selling_price),
):
self.fields[key].widget.attrs["max"] = price
self.fields[key].validators.append(MaxValueValidator(price))
def is_valid(self): def is_valid(self):
return super().is_valid() and self.action_formset.is_valid() return super().is_valid() and self.action_formset.is_valid()
@@ -362,11 +382,25 @@ class ProductFormulaForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
if cleaned_data["result"] in cleaned_data["products"]:
self.add_error(
None,
_(
"The same product cannot be at the same time "
"the result and a part of the formula."
),
)
prices = [p.selling_price for p in cleaned_data["products"]] prices = [p.selling_price for p in cleaned_data["products"]]
special_prices = [p.special_selling_price for p in cleaned_data["products"]]
selling_price = cleaned_data["result"].selling_price selling_price = cleaned_data["result"].selling_price
if selling_price > sum(prices): special_selling_price = cleaned_data["result"].special_selling_price
raise ValidationError( if selling_price > sum(prices) or special_selling_price > sum(special_prices):
_("This formula is more expensive than its constituant products.") self.add_error(
"result",
_(
"The result cannot be more expensive "
"than the total of the other products."
),
) )
return cleaned_data return cleaned_data

View File

@@ -464,6 +464,7 @@ class ProductFormula(models.Model):
) )
result = models.OneToOneField( result = models.OneToOneField(
Product, Product,
related_name="formula",
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name=_("result product"), verbose_name=_("result product"),
help_text=_("The product got with the formula."), help_text=_("The product got with the formula."),
@@ -472,6 +473,18 @@ class ProductFormula(models.Model):
def __str__(self): def __str__(self):
return self.result.name return self.result.name
@cached_property
def max_selling_price(self) -> float:
# iterating over all products is less efficient than doing
# a simple aggregation, but this method is likely to be used in
# coordination with `max_special_selling_price`,
# and Django caches the result of the `all` queryset.
return sum(p.selling_price for p in self.products.all())
@cached_property
def max_special_selling_price(self) -> float:
return sum(p.special_selling_price for p in self.products.all())
class CounterQuerySet(models.QuerySet): class CounterQuerySet(models.QuerySet):
def annotate_has_barman(self, user: User) -> Self: def annotate_has_barman(self, user: User) -> Self: