unify eboutic and regular counter price selection

This commit is contained in:
imperosol
2026-03-08 16:29:48 +01:00
parent e188acc78b
commit 59d7fadf4f
6 changed files with 73 additions and 59 deletions

View File

@@ -119,4 +119,31 @@ class Migration(migrations.Migration):
migrations.RunPython(migrate_prices, reverse_code=migrations.RunPython.noop),
migrations.RemoveField(model_name="product", name="selling_price"),
migrations.RemoveField(model_name="product", name="special_selling_price"),
migrations.AlterField(
model_name="product",
name="description",
field=models.TextField(blank=True, default="", verbose_name="description"),
),
migrations.AlterField(
model_name="product",
name="product_type",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="products",
to="counter.producttype",
verbose_name="product type",
),
),
migrations.AlterField(
model_name="productformula",
name="result",
field=models.OneToOneField(
help_text="The product got with the formula.",
on_delete=django.db.models.deletion.CASCADE,
related_name="formula",
to="counter.product",
verbose_name="result product",
),
),
]

View File

@@ -22,7 +22,7 @@ import string
from datetime import date, datetime, timedelta
from datetime import timezone as tz
from decimal import Decimal
from typing import Literal, Self
from typing import TYPE_CHECKING, Literal, Self
from dict2xml import dict2xml
from django.conf import settings
@@ -47,6 +47,9 @@ from core.utils import get_start_of_semester
from counter.fields import CurrencyField
from subscription.models import Subscription
if TYPE_CHECKING:
from collections.abc import Sequence
def get_eboutic() -> Counter:
return Counter.objects.filter(type="EBOUTIC").order_by("id").first()
@@ -356,13 +359,13 @@ class Product(models.Model):
QUANTITY_FOR_TRAY_PRICE = 6
name = models.CharField(_("name"), max_length=64)
description = models.TextField(_("description"), default="")
description = models.TextField(_("description"), blank=True, default="")
product_type = models.ForeignKey(
ProductType,
related_name="products",
verbose_name=_("product type"),
null=True,
blank=True,
blank=False,
on_delete=models.SET_NULL,
)
code = models.CharField(_("code"), max_length=16, blank=True)
@@ -726,13 +729,20 @@ class Counter(models.Model):
# but they share the same primary key
return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list)
def get_prices_for(self, customer: Customer) -> list[Price]:
return list(
Price.objects.filter(product__counters=self)
def get_prices_for(
self, customer: Customer, *, order_by: Sequence[str] | None = None
) -> list[Price]:
qs = (
Price.objects.filter(
product__counters=self, product__product_type__isnull=False
)
.for_user(customer.user)
.select_related("product", "product__product_type")
.prefetch_related("groups")
)
if order_by:
qs = qs.order_by(*order_by)
return list(qs)
class RefillingQuerySet(models.QuerySet):

View File

@@ -16,7 +16,7 @@ from counter.forms import (
ScheduledProductActionForm,
ScheduledProductActionFormSet,
)
from counter.models import Product, ScheduledProductAction
from counter.models import Product, ProductType, ScheduledProductAction
@pytest.mark.django_db
@@ -47,8 +47,7 @@ def test_create_actions_alongside_product():
form = ProductForm(
data={
"name": "foo",
"description": "bar",
"product_type": product.product_type_id,
"product_type": ProductType.objects.first(),
"club": product.club_id,
"code": "FOO",
"purchase_price": 1.0,
@@ -63,6 +62,7 @@ def test_create_actions_alongside_product():
"action-0-trigger_at": trigger_at,
},
)
form.is_valid()
assert form.is_valid()
product = form.save()
action = ScheduledProductAction.objects.last()

View File

@@ -39,6 +39,7 @@ from counter.models import (
Counter,
Customer,
Permanency,
ProductType,
Refilling,
ReturnableProduct,
Selling,
@@ -238,31 +239,38 @@ class TestCounterClick(TestFullClickBase):
old_subscriber_group = Group.objects.get(
id=settings.SITH_GROUP_OLD_SUBSCRIBERS_ID
)
_product_recipe = product_recipe.extend(product_type=baker.make(ProductType))
cls.gift = price_recipe.make(amount=-1.5, groups=[subscriber_group])
cls.gift = price_recipe.make(
amount=-1.5, groups=[subscriber_group], product=_product_recipe.make()
)
cls.beer = price_recipe.make(
groups=[subscriber_group],
amount=1.5,
product=product_recipe.make(limit_age=18),
product=_product_recipe.make(limit_age=18),
)
cls.beer_tap = price_recipe.make(
groups=[subscriber_group],
amount=1.5,
product=product_recipe.make(limit_age=18, tray=True),
product=_product_recipe.make(limit_age=18, tray=True),
)
cls.snack = price_recipe.make(
groups=[subscriber_group, old_subscriber_group],
amount=1.5,
product=product_recipe.make(limit_age=0),
product=_product_recipe.make(limit_age=0),
)
cls.stamps = price_recipe.make(
groups=[subscriber_group],
amount=1.5,
product=product_recipe.make(limit_age=0),
product=_product_recipe.make(limit_age=0),
)
ReturnableProduct.objects.all().delete()
cls.cons = price_recipe.make(amount=1, groups=[subscriber_group])
cls.dcons = price_recipe.make(amount=-1, groups=[subscriber_group])
cls.cons = price_recipe.make(
amount=1, groups=[subscriber_group], product=_product_recipe.make()
)
cls.dcons = price_recipe.make(
amount=-1, groups=[subscriber_group], product=_product_recipe.make()
)
baker.make(
ReturnableProduct,
product=cls.cons.product,
@@ -575,18 +583,17 @@ class TestCounterClick(TestFullClickBase):
group = baker.make(Group)
customer = baker.make(Customer)
group.users.add(customer.user)
_product_recipe = product_recipe.extend(
counters=[counter], product_type=baker.make(ProductType)
)
price_recipe.make(
_quantity=2,
product=iter(
product_recipe.make(archived=True, counters=[counter], _quantity=2)
),
product=iter(_product_recipe.make(archived=True, _quantity=2)),
groups=[group],
)
unarchived_prices = price_recipe.make(
_quantity=2,
product=iter(
product_recipe.make(archived=False, counters=[counter], _quantity=2)
),
product=iter(_product_recipe.make(archived=False, _quantity=2)),
groups=[group],
)
customer_prices = counter.get_prices_for(customer)

View File

@@ -22,7 +22,7 @@ from typing import Self
from dict2xml import dict2xml
from django.conf import settings
from django.db import DataError, models
from django.db.models import F, OuterRef, Q, Subquery, Sum
from django.db.models import F, OuterRef, Subquery, Sum
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
@@ -39,30 +39,6 @@ from counter.models import (
)
def get_eboutic_prices(user: User) -> list[Price]:
return list(
Price.objects.filter(
Q(is_always_shown=True, groups__in=user.all_groups)
| Q(
id=Subquery(
Price.objects.filter(
product_id=OuterRef("product_id"), groups__in=user.all_groups
)
.order_by("amount")
.values("id")[:1]
)
),
product__product_type__isnull=False,
product__archived=False,
product__limit_age__lte=user.age,
product__counters=get_eboutic(),
)
.select_related("product", "product__product_type")
.order_by("product__product_type__order", "product_id", "amount")
.distinct()
)
class BillingInfoState(Enum):
VALID = 1
EMPTY = 2

View File

@@ -29,9 +29,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import (
LoginRequiredMixin,
)
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import SuspiciousOperation, ValidationError
from django.db import DatabaseError, transaction
@@ -58,14 +56,7 @@ from counter.models import (
Selling,
get_eboutic,
)
from eboutic.models import (
Basket,
BasketItem,
BillingInfoState,
Invoice,
InvoiceItem,
get_eboutic_prices,
)
from eboutic.models import Basket, BasketItem, BillingInfoState, Invoice, InvoiceItem
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
@@ -125,7 +116,10 @@ class EbouticMainView(LoginRequiredMixin, FormView):
@cached_property
def prices(self) -> list[Price]:
return get_eboutic_prices(self.request.user)
return get_eboutic().get_prices_for(
self.customer,
order_by=["product__product_type__order", "product_id", "amount"],
)
@cached_property
def customer(self) -> Customer: