diff --git a/eboutic/tests/test_basket.py b/eboutic/tests/test_basket.py index 60081102..ff7f2077 100644 --- a/eboutic/tests/test_basket.py +++ b/eboutic/tests/test_basket.py @@ -1,3 +1,5 @@ +from datetime import datetime, timezone + import pytest from django.http import HttpResponse from django.test import TestCase @@ -9,8 +11,13 @@ from pytest_django.asserts import assertRedirects from core.baker_recipes import subscriber_user from core.models import Group, User -from counter.baker_recipes import product_recipe -from counter.models import Counter, ProductType, get_eboutic +from counter.baker_recipes import product_recipe, refill_recipe, sale_recipe +from counter.models import ( + Counter, + Customer, + ProductType, + get_eboutic, +) from counter.tests.test_counter import BasketItem from eboutic.models import Basket @@ -24,6 +31,96 @@ def test_get_eboutic(): assert Counter.objects.get(name="Eboutic") == get_eboutic() +@pytest.mark.django_db +def test_eboutic_access_unregistered(client: Client): + eboutic_url = reverse("eboutic:main") + assertRedirects( + client.get(eboutic_url), reverse("core:login", query={"next": eboutic_url}) + ) + + +@pytest.mark.django_db +def test_eboutic_access_new_customer(client: Client): + user = baker.make(User) + assert not Customer.objects.filter(user=user).exists() + + client.force_login(user) + + assert client.get(reverse("eboutic:main")).status_code == 200 + assert Customer.objects.filter(user=user).exists() + + +@pytest.mark.django_db +def test_eboutic_access_old_customer(client: Client): + user = baker.make(User) + customer = Customer.get_or_create(user)[0] + + client.force_login(user) + + assert client.get(reverse("eboutic:main")).status_code == 200 + assert Customer.objects.filter(user=user).first() == customer + + +@pytest.mark.django_db +@pytest.mark.parametrize( + ("sellings", "refillings", "expected"), + ( + ([], [], None), + ( + [datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc)], + [], + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + ), + ( + [], + [datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc)], + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + ), + ( + [datetime(2025, 2, 7, 1, 2, 3, tzinfo=timezone.utc)], + [datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc)], + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + ), + ( + [datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc)], + [datetime(2025, 2, 7, 1, 2, 3, tzinfo=timezone.utc)], + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + ), + ( + [ + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + datetime(2025, 2, 7, 1, 2, 3, tzinfo=timezone.utc), + ], + [datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc)], + datetime(2025, 3, 7, 1, 2, 3, tzinfo=timezone.utc), + ), + ), +) +def test_eboutic_basket_expiry( + client: Client, + sellings: list[datetime], + refillings: list[datetime], + expected: datetime | None, +): + eboutic = get_eboutic() + + customer = baker.make(Customer) + + client.force_login(customer.user) + + for date in sellings: + sale_recipe.make( + customer=customer, counter=eboutic, date=date, is_validated=True + ) + for date in refillings: + refill_recipe.make(customer=customer, counter=eboutic, date=date) + + assert ( + f'x-data="basket({int(expected.timestamp() * 1000) if expected else "null"})"' + in client.get(reverse("eboutic:main")).text + ) + + class TestEboutic(TestCase): @classmethod def setUpTestData(cls): diff --git a/eboutic/views.py b/eboutic/views.py index ec937e9b..bf232621 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -34,6 +34,7 @@ from django.contrib.auth.mixins import ( from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import SuspiciousOperation, ValidationError from django.db import DatabaseError, transaction +from django.db.models import Subquery from django.db.models.fields import forms from django.db.utils import cached_property from django.http import HttpResponse @@ -48,7 +49,14 @@ from django_countries.fields import Country from core.auth.mixins import CanViewMixin from core.views.mixins import FragmentMixin, UseFragmentsMixin from counter.forms import BaseBasketForm, BillingInfoForm, ProductForm -from counter.models import BillingInfo, Customer, Product, Selling, get_eboutic +from counter.models import ( + BillingInfo, + Customer, + Product, + Refilling, + Selling, + get_eboutic, +) from eboutic.models import ( Basket, BasketItem, @@ -124,13 +132,36 @@ class EbouticMainView(LoginRequiredMixin, FormView): context = super().get_context_data(**kwargs) context["products"] = self.products context["customer_amount"] = self.request.user.account_balance - last_purchase: Selling | None = ( - self.customer.buyings.filter(counter__type="EBOUTIC") - .order_by("-date") - .first() - ) + + purchases = ( + Customer.objects.filter(pk=self.customer.pk) + .annotate( + last_refill=Subquery( + Refilling.objects.filter( + counter__type="EBOUTIC", customer_id=self.customer.pk + ) + .order_by("-date") + .values("date")[:1] + ), + last_purchase=Subquery( + Selling.objects.filter( + counter__type="EBOUTIC", customer_id=self.customer.pk + ) + .order_by("-date") + .values("date")[:1] + ), + ) + .values_list("last_refill", "last_purchase") + )[0] + + purchase_times = [ + int(purchase.timestamp() * 1000) + for purchase in purchases + if purchase is not None + ] + context["last_purchase_time"] = ( - int(last_purchase.date.timestamp() * 1000) if last_purchase else "null" + max(purchase_times) if len(purchase_times) > 0 else "null" ) return context