fix: InvoiceQuerySet.annotate_total()

This commit is contained in:
imperosol 2024-10-12 14:58:23 +02:00
parent 1c774aa4a0
commit 7312580a8d
2 changed files with 24 additions and 2 deletions

View File

@ -7,7 +7,7 @@ from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from model_bakery import baker, seq 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 ( from core.baker_recipes import (
old_subscriber_user, old_subscriber_user,
@ -16,6 +16,7 @@ from core.baker_recipes import (
) )
from core.models import User from core.models import User
from counter.models import Counter, Refilling, Selling from counter.models import Counter, Refilling, Selling
from eboutic.models import Invoice, InvoiceItem
class TestSearchUsers(TestCase): class TestSearchUsers(TestCase):
@ -148,3 +149,19 @@ class TestFilterInactive(TestCase):
def test_filter_inactive(self): def test_filter_inactive(self):
res = User.objects.filter(id__in=[u.id for u in self.users]).filter_inactive() res = User.objects.filter(id__in=[u.id for u in self.users]).filter_inactive()
assert list(res) == [self.users[0], self.users[5]] 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]

View File

@ -167,10 +167,15 @@ class InvoiceQueryset(models.QuerySet):
The total amount is the sum of (product_unit_price * quantity) The total amount is the sum of (product_unit_price * quantity)
for all items related to the invoice. 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( return self.annotate(
total=Subquery( total=Subquery(
InvoiceItem.objects.filter(invoice_id=OuterRef("pk")) 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") .values("total")
) )
) )