mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-13 15:15:03 +00:00
Price model
This commit is contained in:
@@ -24,6 +24,7 @@ from counter.models import (
|
||||
Eticket,
|
||||
InvoiceCall,
|
||||
Permanency,
|
||||
Price,
|
||||
Product,
|
||||
ProductType,
|
||||
Refilling,
|
||||
@@ -32,19 +33,24 @@ from counter.models import (
|
||||
)
|
||||
|
||||
|
||||
class PriceInline(admin.TabularInline):
|
||||
model = Price
|
||||
autocomplete_fields = ("groups",)
|
||||
|
||||
|
||||
@admin.register(Product)
|
||||
class ProductAdmin(SearchModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"code",
|
||||
"product_type",
|
||||
"selling_price",
|
||||
"archived",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
list_select_related = ("product_type",)
|
||||
search_fields = ("name", "code")
|
||||
inlines = [PriceInline]
|
||||
|
||||
|
||||
@admin.register(ReturnableProduct)
|
||||
|
||||
@@ -101,13 +101,9 @@ class ProductController(ControllerBase):
|
||||
"""Get the detailed information about the products."""
|
||||
return filters.filter(
|
||||
Product.objects.select_related("club")
|
||||
.prefetch_related("buying_groups")
|
||||
.prefetch_related("prices", "prices__groups")
|
||||
.select_related("product_type")
|
||||
.order_by(
|
||||
F("product_type__order").asc(nulls_last=True),
|
||||
"product_type",
|
||||
"name",
|
||||
)
|
||||
.order_by(F("product_type__order").asc(nulls_last=True), "name")
|
||||
)
|
||||
|
||||
|
||||
|
||||
108
counter/migrations/0038_price.py
Normal file
108
counter/migrations/0038_price.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-18 13:30
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.migrations.state import StateApps
|
||||
|
||||
import counter.fields
|
||||
|
||||
|
||||
def migrate_prices(apps: StateApps, schema_editor):
|
||||
Product = apps.get_model("counter", "Product")
|
||||
Price = apps.get_model("counter", "Price")
|
||||
prices = [
|
||||
Price(
|
||||
amount=p.selling_price,
|
||||
product=p,
|
||||
created_at=p.created_at,
|
||||
updated_at=p.updated_at,
|
||||
)
|
||||
for p in Product.objects.all()
|
||||
]
|
||||
Price.objects.bulk_create(prices)
|
||||
groups = [
|
||||
Price.groups.through(price=price, group=group)
|
||||
for price in Price.objects.select_related("product").prefetch_related(
|
||||
"product__buying_groups"
|
||||
)
|
||||
for group in price.product.buying_groups.all()
|
||||
]
|
||||
Price.groups.through.objects.bulk_create(groups)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0048_alter_user_options"),
|
||||
("counter", "0037_productformula"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Price",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"amount",
|
||||
counter.fields.CurrencyField(
|
||||
decimal_places=2, max_digits=12, verbose_name="amount"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_always_shown",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text=(
|
||||
"If this option is enabled, "
|
||||
"people will see this price and be able to pay it, "
|
||||
"even if another cheaper price exists. "
|
||||
"Else it will visible only if it is the cheapest available price."
|
||||
),
|
||||
verbose_name="always show",
|
||||
),
|
||||
),
|
||||
(
|
||||
"label",
|
||||
models.CharField(
|
||||
default="",
|
||||
help_text="A short label for easier differentiation if a user can see multiple prices.",
|
||||
max_length=32,
|
||||
verbose_name="label",
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created at"),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(auto_now=True, verbose_name="updated at"),
|
||||
),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
related_name="prices", to="core.group", verbose_name="groups"
|
||||
),
|
||||
),
|
||||
(
|
||||
"product",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="prices",
|
||||
to="counter.product",
|
||||
verbose_name="product",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "price"},
|
||||
),
|
||||
migrations.RunPython(migrate_prices, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
@@ -456,6 +456,78 @@ class Product(models.Model):
|
||||
return self.selling_price - self.purchase_price
|
||||
|
||||
|
||||
class PriceQuerySet(models.QuerySet):
|
||||
def for_user(self, user: User) -> Self:
|
||||
age = user.age
|
||||
if user.is_banned_alcohol:
|
||||
age = min(age, 17)
|
||||
return self.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__archived=False,
|
||||
product__limit_age__lte=age,
|
||||
)
|
||||
|
||||
|
||||
class Price(models.Model):
|
||||
amount = CurrencyField(_("amount"))
|
||||
product = models.ForeignKey(
|
||||
Product,
|
||||
verbose_name=_("product"),
|
||||
related_name="prices",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
groups = models.ManyToManyField(
|
||||
Group, verbose_name=_("groups"), related_name="prices"
|
||||
)
|
||||
is_always_shown = models.BooleanField(
|
||||
_("always show"),
|
||||
help_text=_(
|
||||
"If this option is enabled, "
|
||||
"people will see this price and be able to pay it, "
|
||||
"even if another cheaper price exists. "
|
||||
"Else it will visible only if it is the cheapest available price."
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
label = models.CharField(
|
||||
_("label"),
|
||||
help_text=_(
|
||||
"A short label for easier differentiation "
|
||||
"if a user can see multiple prices."
|
||||
),
|
||||
max_length=32,
|
||||
default="",
|
||||
blank=True,
|
||||
)
|
||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
|
||||
|
||||
objects = PriceQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("price")
|
||||
|
||||
def __str__(self):
|
||||
if not self.label:
|
||||
return f"{self.product.name} ({self.amount}€)"
|
||||
return f"{self.product.name} {self.label} ({self.amount}€)"
|
||||
|
||||
@property
|
||||
def full_label(self):
|
||||
if not self.label:
|
||||
return self.product.name
|
||||
return f"{self.product.name} \u2013 {self.label}"
|
||||
|
||||
|
||||
class ProductFormula(models.Model):
|
||||
products = models.ManyToManyField(
|
||||
Product,
|
||||
@@ -1001,7 +1073,9 @@ class Selling(models.Model):
|
||||
event = self.product.eticket.event_title or _("Unknown event")
|
||||
subject = _("Eticket bought for the event %(event)s") % {"event": event}
|
||||
message_html = _(
|
||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
||||
"You bought an eticket for the event %(event)s.\n"
|
||||
"You can download it directly from this link %(eticket)s.\n"
|
||||
"You can also retrieve all your e-tickets on your account page %(url)s."
|
||||
) % {
|
||||
"event": event,
|
||||
"url": (
|
||||
|
||||
@@ -6,8 +6,8 @@ from ninja import FilterLookup, FilterSchema, ModelSchema, Schema
|
||||
from pydantic import model_validator
|
||||
|
||||
from club.schemas import SimpleClubSchema
|
||||
from core.schemas import GroupSchema, NonEmptyStr, SimpleUserSchema
|
||||
from counter.models import Counter, Product, ProductType
|
||||
from core.schemas import NonEmptyStr, SimpleUserSchema
|
||||
from counter.models import Counter, Price, Product, ProductType
|
||||
|
||||
|
||||
class CounterSchema(ModelSchema):
|
||||
@@ -66,6 +66,12 @@ class SimpleProductSchema(ModelSchema):
|
||||
fields = ["id", "name", "code"]
|
||||
|
||||
|
||||
class ProductPriceSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = Price
|
||||
fields = ["amount", "groups"]
|
||||
|
||||
|
||||
class ProductSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = Product
|
||||
@@ -75,13 +81,12 @@ class ProductSchema(ModelSchema):
|
||||
"code",
|
||||
"description",
|
||||
"purchase_price",
|
||||
"selling_price",
|
||||
"icon",
|
||||
"limit_age",
|
||||
"archived",
|
||||
]
|
||||
|
||||
buying_groups: list[GroupSchema]
|
||||
prices: list[ProductPriceSchema]
|
||||
club: SimpleClubSchema
|
||||
product_type: SimpleProductTypeSchema | None
|
||||
url: str
|
||||
|
||||
Reference in New Issue
Block a user