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,
|
Eticket,
|
||||||
InvoiceCall,
|
InvoiceCall,
|
||||||
Permanency,
|
Permanency,
|
||||||
|
Price,
|
||||||
Product,
|
Product,
|
||||||
ProductType,
|
ProductType,
|
||||||
Refilling,
|
Refilling,
|
||||||
@@ -32,19 +33,24 @@ from counter.models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PriceInline(admin.TabularInline):
|
||||||
|
model = Price
|
||||||
|
autocomplete_fields = ("groups",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
@admin.register(Product)
|
||||||
class ProductAdmin(SearchModelAdmin):
|
class ProductAdmin(SearchModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"name",
|
"name",
|
||||||
"code",
|
"code",
|
||||||
"product_type",
|
"product_type",
|
||||||
"selling_price",
|
|
||||||
"archived",
|
"archived",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
)
|
)
|
||||||
list_select_related = ("product_type",)
|
list_select_related = ("product_type",)
|
||||||
search_fields = ("name", "code")
|
search_fields = ("name", "code")
|
||||||
|
inlines = [PriceInline]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ReturnableProduct)
|
@admin.register(ReturnableProduct)
|
||||||
|
|||||||
@@ -101,13 +101,9 @@ class ProductController(ControllerBase):
|
|||||||
"""Get the detailed information about the products."""
|
"""Get the detailed information about the products."""
|
||||||
return filters.filter(
|
return filters.filter(
|
||||||
Product.objects.select_related("club")
|
Product.objects.select_related("club")
|
||||||
.prefetch_related("buying_groups")
|
.prefetch_related("prices", "prices__groups")
|
||||||
.select_related("product_type")
|
.select_related("product_type")
|
||||||
.order_by(
|
.order_by(F("product_type__order").asc(nulls_last=True), "name")
|
||||||
F("product_type__order").asc(nulls_last=True),
|
|
||||||
"product_type",
|
|
||||||
"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
|
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):
|
class ProductFormula(models.Model):
|
||||||
products = models.ManyToManyField(
|
products = models.ManyToManyField(
|
||||||
Product,
|
Product,
|
||||||
@@ -1001,7 +1073,9 @@ class Selling(models.Model):
|
|||||||
event = self.product.eticket.event_title or _("Unknown event")
|
event = self.product.eticket.event_title or _("Unknown event")
|
||||||
subject = _("Eticket bought for the event %(event)s") % {"event": event}
|
subject = _("Eticket bought for the event %(event)s") % {"event": event}
|
||||||
message_html = _(
|
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,
|
"event": event,
|
||||||
"url": (
|
"url": (
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from ninja import FilterLookup, FilterSchema, ModelSchema, Schema
|
|||||||
from pydantic import model_validator
|
from pydantic import model_validator
|
||||||
|
|
||||||
from club.schemas import SimpleClubSchema
|
from club.schemas import SimpleClubSchema
|
||||||
from core.schemas import GroupSchema, NonEmptyStr, SimpleUserSchema
|
from core.schemas import NonEmptyStr, SimpleUserSchema
|
||||||
from counter.models import Counter, Product, ProductType
|
from counter.models import Counter, Price, Product, ProductType
|
||||||
|
|
||||||
|
|
||||||
class CounterSchema(ModelSchema):
|
class CounterSchema(ModelSchema):
|
||||||
@@ -66,6 +66,12 @@ class SimpleProductSchema(ModelSchema):
|
|||||||
fields = ["id", "name", "code"]
|
fields = ["id", "name", "code"]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductPriceSchema(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = Price
|
||||||
|
fields = ["amount", "groups"]
|
||||||
|
|
||||||
|
|
||||||
class ProductSchema(ModelSchema):
|
class ProductSchema(ModelSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Product
|
model = Product
|
||||||
@@ -75,13 +81,12 @@ class ProductSchema(ModelSchema):
|
|||||||
"code",
|
"code",
|
||||||
"description",
|
"description",
|
||||||
"purchase_price",
|
"purchase_price",
|
||||||
"selling_price",
|
|
||||||
"icon",
|
"icon",
|
||||||
"limit_age",
|
"limit_age",
|
||||||
"archived",
|
"archived",
|
||||||
]
|
]
|
||||||
|
|
||||||
buying_groups: list[GroupSchema]
|
prices: list[ProductPriceSchema]
|
||||||
club: SimpleClubSchema
|
club: SimpleClubSchema
|
||||||
product_type: SimpleProductTypeSchema | None
|
product_type: SimpleProductTypeSchema | None
|
||||||
url: str
|
url: str
|
||||||
|
|||||||
Reference in New Issue
Block a user