Merge pull request #1246 from ae-utbm/club-sale

use FilterSchema for club sales view
This commit is contained in:
thomas girod
2025-11-14 19:59:57 +01:00
committed by GitHub
5 changed files with 101 additions and 42 deletions

View File

@@ -37,6 +37,7 @@ from core.views.widgets.ajax_select import (
AutoCompleteSelectUser,
)
from counter.models import Counter, Selling
from counter.schemas import SaleFilterSchema
class ClubEditForm(forms.ModelForm):
@@ -191,6 +192,18 @@ class SellingsForm(forms.Form):
required=False,
)
def to_filter_schema(self) -> SaleFilterSchema:
products = (
*self.cleaned_data["products"],
*self.cleaned_data["archived_products"],
)
return SaleFilterSchema(
after=self.cleaned_data["begin_date"],
before=self.cleaned_data["end_date"],
counters={c.id for c in self.cleaned_data["counters"]} or None,
products={p.id for p in products} or None,
)
class ClubOldMemberForm(forms.Form):
members_old = forms.ModelMultipleChoiceField(

View File

@@ -1,3 +1,6 @@
import csv
import itertools
import pytest
from django.test import Client
from django.urls import reverse
@@ -7,16 +10,20 @@ from club.forms import SellingsForm
from club.models import Club
from core.models import User
from counter.baker_recipes import product_recipe, sale_recipe
from counter.models import Counter, Customer
from counter.models import Counter, Customer, Product, Selling
@pytest.mark.django_db
def test_sales_page_doesnt_crash(client: Client):
"""Basic crashtest on club sales view."""
club = baker.make(Club)
product = baker.make(Product, club=club)
admin = baker.make(User, is_superuser=True)
client.force_login(admin)
response = client.get(reverse("club:club_sellings", kwargs={"club_id": club.id}))
assert response.status_code == 200
url = reverse("club:club_sellings", kwargs={"club_id": club.id})
assert client.get(url).status_code == 200
assert client.post(url).status_code == 200
assert client.post(url, data={"products": [product.id]}).status_code == 200
@pytest.mark.django_db
@@ -36,3 +43,62 @@ def test_sales_form_counter_filter():
form = SellingsForm(club)
form_counters = list(form.fields["counters"].queryset)
assert form_counters == [counters[1], counters[2], counters[0]]
@pytest.mark.django_db
def test_club_sales_csv(client: Client):
client.force_login(baker.make(User, is_superuser=True))
club = baker.make(Club)
counter = baker.make(Counter, club=club)
product = product_recipe.make(club=club, counters=[counter], purchase_price=0.5)
customers = baker.make(Customer, amount=100, _quantity=2, _bulk_create=True)
sales: list[Selling] = sale_recipe.make(
club=club,
counter=counter,
quantity=2,
unit_price=1.5,
product=iter([product, product, None]),
customer=itertools.cycle(customers),
_quantity=3,
)
url = reverse("club:sellings_csv", kwargs={"club_id": club.id})
response = client.post(url, data={"counters": [counter.id]})
assert response.status_code == 200
reader = csv.reader(s.decode() for s in response.streaming_content)
data = list(reader)
sale_rows = [
[
str(s.date),
str(counter),
str(s.seller),
s.customer.user.get_display_name(),
s.label,
"2",
"1.50",
"3.00",
"Compte utilisateur",
]
for s in sales[::-1]
]
sale_rows[2].extend(["0.50", "1.00"])
sale_rows[1].extend(["0.50", "1.00"])
sale_rows[0].extend(["", ""])
assert data == [
["Quantité", "6"],
["Total", "9"],
["Bénéfice", "1"],
[
"Date",
"Comptoir",
"Barman",
"Client",
"Étiquette",
"Quantité",
"Prix unitaire",
"Total",
"Méthode de paiement",
"Prix d'achat",
"Bénéfice",
],
*sale_rows,
]

View File

@@ -406,33 +406,14 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView):
kwargs = super().get_context_data(**kwargs)
kwargs["result"] = Selling.objects.none()
kwargs["paginated_result"] = kwargs["result"]
kwargs["total"] = 0
kwargs["total_quantity"] = 0
kwargs["benefit"] = 0
form = self.get_form()
if form.is_valid():
qs = Selling.objects.filter(club=self.object)
if not len([v for v in form.cleaned_data.values() if v is not None]):
qs = Selling.objects.none()
if form.cleaned_data["begin_date"]:
qs = qs.filter(date__gte=form.cleaned_data["begin_date"])
if form.cleaned_data["end_date"]:
qs = qs.filter(date__lte=form.cleaned_data["end_date"])
if form.cleaned_data["counters"]:
qs = qs.filter(counter__in=form.cleaned_data["counters"])
selected_products = []
if form.cleaned_data["products"]:
selected_products.extend(form.cleaned_data["products"])
if form.cleaned_data["archived_products"]:
selected_products.extend(form.cleaned_data["archived_products"])
if len(selected_products) > 0:
qs = qs.filter(product__in=selected_products)
form: SellingsForm = self.get_form()
if form.is_valid() and any(v for v in form.cleaned_data.values()):
filters = form.to_filter_schema()
qs = filters.filter(Selling.objects.filter(club=self.object))
kwargs["total"] = qs.annotate(
price=F("quantity") * F("unit_price")
).aggregate(total=Sum("price", default=0))["total"]
@@ -479,15 +460,15 @@ class ClubSellingCSVView(ClubSellingView):
*row,
selling.label,
selling.quantity,
selling.unit_price,
selling.quantity * selling.unit_price,
selling.get_payment_method_display(),
]
if selling.product:
row.append(selling.product.selling_price)
row.append(selling.product.purchase_price)
row.append(selling.product.selling_price - selling.product.purchase_price)
row.append(selling.unit_price - selling.product.purchase_price)
else:
row = [*row, "", "", ""]
row = [*row, "", ""]
return row
def get(self, request, *args, **kwargs):
@@ -508,9 +489,9 @@ class ClubSellingCSVView(ClubSellingView):
gettext("Customer"),
gettext("Label"),
gettext("Quantity"),
gettext("Unit price"),
gettext("Total"),
gettext("Payment method"),
gettext("Selling price"),
gettext("Purchase price"),
gettext("Benefit"),
],

View File

@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Annotated, Self
from annotated_types import MinLen
@@ -100,3 +101,10 @@ class ProductFilterSchema(FilterSchema):
product_type: set[int] | None = Field(None, q="product_type__in")
club: set[int] | None = Field(None, q="club__in")
counter: set[int] | None = Field(None, q="counters__in")
class SaleFilterSchema(FilterSchema):
before: datetime | None = Field(None, q="date__lt")
after: datetime | None = Field(None, q="date__gt")
counters: set[int] | None = Field(None, q="counter__in")
products: set[int] | None = Field(None, q="product__in")

View File

@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-11 13:52+0100\n"
"POT-Creation-Date: 2025-11-12 21:44+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -704,8 +704,8 @@ msgid "Benefit"
msgstr "Bénéfice"
#: club/views.py
msgid "Selling price"
msgstr "Prix de vente"
msgid "Unit price"
msgstr "Prix unitaire"
#: club/views.py
msgid "Purchase price"
@@ -5666,12 +5666,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format
msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s"
#~ msgid "is viewable"
#~ msgstr "profil visible"
#~ msgid "One month for free"
#~ msgstr "Un mois gratuit"
#~ msgid "Two months for free"
#~ msgstr "Deux mois gratuits"