From 7312580a8d081378d7b966c0715e4da52243f0ee Mon Sep 17 00:00:00 2001 From: imperosol Date: Sat, 12 Oct 2024 14:58:23 +0200 Subject: [PATCH] fix: InvoiceQuerySet.annotate_total() --- core/tests/test_user.py | 19 ++++++++++++++++++- eboutic/models.py | 7 ++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/core/tests/test_user.py b/core/tests/test_user.py index 25125d5c..c767f3f4 100644 --- a/core/tests/test_user.py +++ b/core/tests/test_user.py @@ -7,7 +7,7 @@ from django.test import Client, TestCase from django.urls import reverse from django.utils.timezone import now from model_bakery import baker, seq -from model_bakery.recipe import Recipe +from model_bakery.recipe import Recipe, foreign_key from core.baker_recipes import ( old_subscriber_user, @@ -16,6 +16,7 @@ from core.baker_recipes import ( ) from core.models import User from counter.models import Counter, Refilling, Selling +from eboutic.models import Invoice, InvoiceItem class TestSearchUsers(TestCase): @@ -148,3 +149,19 @@ class TestFilterInactive(TestCase): def test_filter_inactive(self): res = User.objects.filter(id__in=[u.id for u in self.users]).filter_inactive() assert list(res) == [self.users[0], self.users[5]] + + +@pytest.mark.django_db +def test_user_invoice_with_multiple_items(): + """Test that annotate_total() works when invoices contain multiple items.""" + user: User = subscriber_user.make() + item_recipe = Recipe(InvoiceItem, invoice=foreign_key(Recipe(Invoice, user=user))) + item_recipe.make(_quantity=3, quantity=1, product_unit_price=5) + item_recipe.make(_quantity=1, quantity=1, product_unit_price=5) + res = list( + Invoice.objects.filter(user=user) + .annotate_total() + .order_by("-total") + .values_list("total", flat=True) + ) + assert res == [15, 5] diff --git a/eboutic/models.py b/eboutic/models.py index 0b8c30e1..468562ad 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -167,10 +167,15 @@ class InvoiceQueryset(models.QuerySet): The total amount is the sum of (product_unit_price * quantity) for all items related to the invoice. """ + # aggregates within subqueries require a little bit of black magic, + # but hopefully, django gives a comprehensive documentation for that : + # https://docs.djangoproject.com/en/stable/ref/models/expressions/#using-aggregates-within-a-subquery-expression return self.annotate( total=Subquery( InvoiceItem.objects.filter(invoice_id=OuterRef("pk")) - .annotate(total=Sum(F("product_unit_price") * F("quantity"))) + .annotate(item_amount=F("product_unit_price") * F("quantity")) + .values("item_amount") + .annotate(total=Sum("item_amount")) .values("total") ) )