diff --git a/core/templates/core/user_stats.jinja b/core/templates/core/user_stats.jinja index 9b900117..621c4956 100644 --- a/core/templates/core/user_stats.jinja +++ b/core/templates/core/user_stats.jinja @@ -11,32 +11,35 @@ {% block content %}
- {% if profile.permanencies %} + {% if total_perm_time %}

{% trans %}Permanencies{% endtrans %}

-
Foyer :{{ total_foyer_time }}
-
Gommette :{{ total_gommette_time }}
-
MDE :{{ total_mde_time }}
-
Total :{{ total_perm_time }}
+ {% for perm in perm_time %} +
+ {{ perm["counter__name"] }} : + {{ perm["total"]|format_timedelta }} +
+ {% endfor %} +
Total :{{ total_perm_time|format_timedelta }}
{% endif %} -

{% trans %}Buyings{% endtrans %}

-
Foyer :{{ total_foyer_buyings }} €
-
Gommette :{{ total_gommette_buyings }} €
-
MDE :{{ total_mde_buyings }} €
-
Total :{{ total_foyer_buyings + total_gommette_buyings + total_mde_buyings }} € -
+ {% for sum in purchase_sums %} +
+ {{ sum["counter__name"] }} + {{ sum["total"] }} € +
+ {% endfor %} +
Total : {{ total_purchases }} €
-
-

{% trans %}Product top 10{% endtrans %}

+

{% trans %}Product top 15{% endtrans %}

diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 62f9f730..927acf54 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -55,31 +55,17 @@ def phonenumber( return value -@register.filter(name="truncate_time") -def truncate_time(value, time_unit): - """Remove everything in the time format lower than the specified unit. - - Args: - value: the value to truncate - time_unit: the lowest unit to display - """ - value = str(value) - return { - "millis": lambda: value.split(".")[0], - "seconds": lambda: value.rsplit(":", maxsplit=1)[0], - "minutes": lambda: value.split(":", maxsplit=1)[0], - "hours": lambda: value.rsplit(" ")[0], - }[time_unit]() - - @register.filter(name="format_timedelta") def format_timedelta(value: datetime.timedelta) -> str: + value = value - datetime.timedelta(microseconds=value.microseconds) days = value.days if days == 0: return str(value) remainder = value - datetime.timedelta(days=days) return ngettext( - "%(nb_days)d day, %(remainder)s", "%(nb_days)d days, %(remainder)s", days + "%(nb_days)d day, %(remainder)s", + "%(nb_days)d days, %(remainder)s", + days, ) % {"nb_days": days, "remainder": str(remainder)} diff --git a/core/tests/test_user.py b/core/tests/test_user.py index b90e626e..62a05e1b 100644 --- a/core/tests/test_user.py +++ b/core/tests/test_user.py @@ -1,3 +1,4 @@ +import itertools from datetime import timedelta from unittest import mock @@ -23,7 +24,7 @@ from core.baker_recipes import ( from core.models import AnonymousUser, Group, User from core.views import UserTabsMixin from counter.baker_recipes import sale_recipe -from counter.models import Counter, Customer, Refilling, Selling +from counter.models import Counter, Customer, Permanency, Refilling, Selling from counter.utils import is_logged_in_counter from eboutic.models import Invoice, InvoiceItem @@ -424,3 +425,28 @@ class TestUserQuerySetViewableBy: user = user_factory() viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user) assert not viewable.exists() + + +@pytest.mark.django_db +def test_user_stats(client: Client): + user = subscriber_user.make() + baker.make(Refilling, customer=user.customer, amount=99999) + bars = [b[0] for b in settings.SITH_COUNTER_BARS] + baker.make( + Permanency, + end=now() - timedelta(days=5), + start=now() - timedelta(days=5, hours=3), + counter_id=itertools.cycle(bars), + _quantity=5, + _bulk_create=True, + ) + sale_recipe.make( + counter_id=itertools.cycle(bars), + customer=user.customer, + unit_price=1, + quantity=1, + _quantity=5, + ) + client.force_login(user) + response = client.get(reverse("core:user_stats", kwargs={"user_id": user.id})) + assert response.status_code == 200 diff --git a/core/views/user.py b/core/views/user.py index 4b9c32cb..b3f4831b 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -22,9 +22,9 @@ # # import itertools +from datetime import timedelta # This file contains all the views that concern the user model -from datetime import date, timedelta from operator import itemgetter from smtplib import SMTPException @@ -32,7 +32,7 @@ from django.contrib.auth import login, views from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied -from django.db.models import DateField, QuerySet +from django.db.models import DateField, F, QuerySet, Sum from django.db.models.functions import Trunc from django.forms.models import modelform_factory from django.http import Http404 @@ -66,9 +66,8 @@ from core.views.forms import ( UserProfileForm, ) from core.views.mixins import TabedViewMixin, UseFragmentsMixin -from counter.models import Counter, Refilling, Selling +from counter.models import Refilling, Selling from eboutic.models import Invoice -from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -353,87 +352,40 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): context_object_name = "profile" template_name = "core/user_stats.jinja" current_tab = "stats" + queryset = User.objects.exclude(customer=None).select_related("customer") def dispatch(self, request, *arg, **kwargs): profile = self.get_object() - - if not hasattr(profile, "customer"): - raise Http404 - if not ( profile == request.user or request.user.has_perm("counter.view_customer") ): raise PermissionDenied - return super().dispatch(request, *arg, **kwargs) def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) - from django.db.models import Sum - foyer = Counter.objects.filter(name="Foyer").first() - mde = Counter.objects.filter(name="MDE").first() - gommette = Counter.objects.filter(name="La Gommette").first() - semester_start = Subscription.compute_start(d=date.today(), duration=3) + kwargs["perm_time"] = list( + self.object.permanencies.filter(end__isnull=False, counter__type="BAR") + .values("counter", "counter__name") + .annotate(total=Sum(F("end") - F("start"), default=timedelta(seconds=0))) + .order_by("-total") + ) kwargs["total_perm_time"] = sum( - [p.end - p.start for p in self.object.permanencies.exclude(end=None)], - timedelta(), + [perm["total"] for perm in kwargs["perm_time"]], start=timedelta(seconds=0) ) - kwargs["total_foyer_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=foyer).exclude( - end=None - ) - ], - timedelta(), - ) - kwargs["total_mde_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=mde).exclude(end=None) - ], - timedelta(), - ) - kwargs["total_gommette_time"] = sum( - [ - p.end - p.start - for p in self.object.permanencies.filter(counter=gommette).exclude( - end=None - ) - ], - timedelta(), - ) - kwargs["total_foyer_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=foyer, date__gte=semester_start - ) - ] - ) - kwargs["total_mde_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=mde, date__gte=semester_start - ) - ] - ) - kwargs["total_gommette_buyings"] = sum( - [ - b.unit_price * b.quantity - for b in self.object.customer.buyings.filter( - counter=gommette, date__gte=semester_start - ) - ] + kwargs["purchase_sums"] = list( + self.object.customer.buyings.filter(counter__type="BAR") + .values("counter", "counter__name") + .annotate(total=Sum(F("unit_price") * F("quantity"))) + .order_by("-total") ) + kwargs["total_purchases"] = sum(s["total"] for s in kwargs["purchase_sums"]) kwargs["top_product"] = ( self.object.customer.buyings.values("product__name") .annotate(product_sum=Sum("quantity")) - .exclude(product_sum=None) .order_by("-product_sum") - .all()[:10] + .all()[:15] ) return kwargs diff --git a/counter/templates/counter/stats.jinja b/counter/templates/counter/stats.jinja index 5534175a..ccea341b 100644 --- a/counter/templates/counter/stats.jinja +++ b/counter/templates/counter/stats.jinja @@ -51,7 +51,7 @@ - + {% endfor %} @@ -73,7 +73,7 @@ - + {% endfor %} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 6be36d8f..de4ade36 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-19 21:00+0100\n" +"POT-Creation-Date: 2025-11-24 11:05+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -2658,8 +2658,8 @@ msgid "Buyings" msgstr "Achats" #: core/templates/core/user_stats.jinja -msgid "Product top 10" -msgstr "Top 10 produits" +msgid "Product top 15" +msgstr "Top 15 produits" #: core/templates/core/user_stats.jinja msgid "Product" @@ -2819,8 +2819,8 @@ msgstr "Outils Trombi" #, python-format msgid "%(nb_days)d day, %(remainder)s" msgid_plural "%(nb_days)d days, %(remainder)s" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(nb_days)d jour, %(remainder)s" +msgstr[1] "%(nb_days)d jours, %(remainder)s" #: core/views/files.py msgid "Add a new folder" diff --git a/sith/settings.py b/sith/settings.py index 6a266eba..313722e0 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -177,7 +177,6 @@ TEMPLATES = [ "filters": { "markdown": "core.templatetags.renderer.markdown", "phonenumber": "core.templatetags.renderer.phonenumber", - "truncate_time": "core.templatetags.renderer.truncate_time", "format_timedelta": "core.templatetags.renderer.format_timedelta", "add_attr": "core.templatetags.renderer.add_attr", },
{{ loop.index }} {{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %} {{ barman.promo or '' }}{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}{{ barman.perm_sum|format_timedelta }}
{{ loop.index }} {{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %} {{ barman.promo or '' }}{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}{{ barman.perm_sum|format_timedelta }}