Pre-filter allowed products in backend for counter click

This commit is contained in:
Antoine Bartuccio 2024-12-22 01:56:57 +01:00
parent c37288c285
commit eea237b813
2 changed files with 55 additions and 54 deletions

View File

@ -428,13 +428,6 @@ class Product(models.Model):
def profit(self): def profit(self):
return self.selling_price - self.purchase_price return self.selling_price - self.purchase_price
def get_actual_price(self, counter: Counter, customer: Customer):
"""Return the price of the article taking into account if the customer has a special price
or not in the counter it's being purchased on"""
if counter.customer_is_barman(customer):
return self.special_selling_price
return self.selling_price
class CounterQuerySet(models.QuerySet): class CounterQuerySet(models.QuerySet):
def annotate_has_barman(self, user: User) -> Self: def annotate_has_barman(self, user: User) -> Self:

View File

@ -53,13 +53,15 @@ class ProductForm(Form):
def __init__( def __init__(
self, self,
customer: Customer,
counter: Counter,
allowed_products: list[Product],
*args, *args,
customer: Customer | None = None,
counter: Counter | None = None,
**kwargs, **kwargs,
): ):
self.customer = customer self.customer = customer # Used by formset
self.counter = counter self.counter = counter # Used by formset
self.allowed_products = allowed_products
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def clean(self): def clean(self):
@ -67,45 +69,27 @@ class ProductForm(Form):
if len(self.errors) > 0: if len(self.errors) > 0:
return return
if self.customer is None or self.counter is None:
raise RuntimeError(
f"{self} has been initialized without customer or counter"
)
user = self.customer.user
# We store self.product so we can use it later on the formset validation # We store self.product so we can use it later on the formset validation
self.product = self.counter.products.filter(id=cleaned_data["id"]).first() self.product = next(
(
product
for product in self.allowed_products
if product.id == cleaned_data["id"]
),
None,
)
if self.product is None: if self.product is None:
raise ValidationError( raise ValidationError(
_( _("The selected product isn't available for this user")
"Product %(product)s doesn't exist or isn't available on this counter"
)
% {"product": cleaned_data["id"]}
) )
# Test alcohoolic products
if self.product.limit_age >= 18:
if not user.date_of_birth:
raise ValidationError(_("Too young for that product"))
if user.is_banned_alcohol:
raise ValidationError(_("Not allowed for that product"))
if user.date_of_birth and self.customer.user.get_age() < self.product.limit_age:
raise ValidationError(_("Too young for that product"))
if user.is_banned_counter:
raise ValidationError(_("Not allowed for that product"))
# Compute prices # Compute prices
cleaned_data["bonus_quantity"] = 0 cleaned_data["bonus_quantity"] = 0
if self.product.tray: if self.product.tray:
cleaned_data["bonus_quantity"] = math.floor( cleaned_data["bonus_quantity"] = math.floor(
cleaned_data["quantity"] / Product.QUANTITY_FOR_TRAY_PRICE cleaned_data["quantity"] / Product.QUANTITY_FOR_TRAY_PRICE
) )
cleaned_data["unit_price"] = self.product.get_actual_price( cleaned_data["total_price"] = self.product.price * (
self.counter, self.customer
)
cleaned_data["total_price"] = cleaned_data["unit_price"] * (
cleaned_data["quantity"] - cleaned_data["bonus_quantity"] cleaned_data["quantity"] - cleaned_data["bonus_quantity"]
) )
@ -164,27 +148,62 @@ class CounterClick(CounterTabsMixin, CanViewMixin, SingleObjectMixin, FormView):
pk_url_kwarg = "counter_id" pk_url_kwarg = "counter_id"
current_tab = "counter" current_tab = "counter"
def get_products(self) -> list[Product]:
"""Get all allowed products for the current customer on the current counter"""
if hasattr(self, "_products"):
return self._products
products = self.object.products.select_related("product_type").prefetch_related(
"buying_groups"
)
# Only include allowed products
if not self.customer.user.date_of_birth or self.customer.user.is_banned_alcohol:
products = products.filter(limit_age__lt=18)
else:
products = products.filter(limit_age__lte=self.customer.user.get_age())
# Compute special price for customer if he is a barmen on that bar
if self.object.customer_is_barman(self.customer):
products = products.annotate(price=F("special_selling_price"))
else:
products = products.annotate(price=F("selling_price"))
self._products = [
product
for product in products.all()
if product.can_be_sold_to(self.customer.user)
]
return self._products
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["form_kwargs"] = { kwargs["form_kwargs"] = {
"customer": self.customer, "customer": self.customer,
"counter": self.object, "counter": self.object,
"allowed_products": self.get_products(),
} }
return kwargs return kwargs
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"]) self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"])
obj: Counter = self.get_object() obj: Counter = self.get_object()
if not self.customer.can_buy:
raise Http404 if not self.customer.can_buy or self.customer.user.is_banned_counter:
return redirect(obj) # Redirect to counter
if obj.type != "BAR" and not request.user.is_authenticated: if obj.type != "BAR" and not request.user.is_authenticated:
raise PermissionDenied raise PermissionDenied
if obj.type == "BAR" and ( if obj.type == "BAR" and (
"counter_token" not in request.session "counter_token" not in request.session
or request.session["counter_token"] != obj.token or request.session["counter_token"] != obj.token
or len(obj.barmen_list) == 0 or len(obj.barmen_list) == 0
): ):
return redirect(obj) # Redirect to counter return redirect(obj) # Redirect to counter
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def form_valid(self, formset): def form_valid(self, formset):
@ -207,7 +226,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, SingleObjectMixin, FormView):
product=form.product, product=form.product,
club=form.product.club, club=form.product.club,
counter=self.object, counter=self.object,
unit_price=form.cleaned_data["unit_price"], unit_price=form.product.price,
quantity=form.cleaned_data["quantity"] quantity=form.cleaned_data["quantity"]
- form.cleaned_data["bonus_quantity"], - form.cleaned_data["bonus_quantity"],
seller=operator, seller=operator,
@ -238,21 +257,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, SingleObjectMixin, FormView):
def get_success_url(self): def get_success_url(self):
return resolve_url(self.object) return resolve_url(self.object)
def get_product(self, pid):
return Product.objects.filter(pk=int(pid)).first()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add customer to the context.""" """Add customer to the context."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
products = self.object.products.select_related("product_type") kwargs["products"] = self.get_products()
# Optimisation to bulk edit prices instead of `calling get_actual_price` on everything
if self.object.customer_is_barman(self.customer):
products = products.annotate(price=F("special_selling_price"))
else:
products = products.annotate(price=F("selling_price"))
kwargs["products"] = products
kwargs["categories"] = {} kwargs["categories"] = {}
for product in kwargs["products"]: for product in kwargs["products"]:
if product.product_type: if product.product_type: