Менеджер моделей queryset удваивает сумму для наборов сумм

Я хочу вернуть суммы элементов и платежей для счета-фактуры, используя менеджер моделей. Это работает правильно, если в моделях платежей и элементов есть только одна запись, если я добавляю больше, я больше не могу выстроить цепочку результатов. В оболочке я попробовал следующее

>>> Invoixe.objects.with_aggregates().last().tot
Decimal('60')
>>> Invoixe.objects.with_totals().last().tot
Decimal('30')

with_aggregates возвращает неверное значение total(tot) (Правильный итог - 30)

SQL выглядит следующим образом

SELECT
  "task_invoixe"."id",
  "task_invoixe"."name",
  (CAST(SUM("task_ixem"."amount") AS NUMERIC)) AS "tot",
  (CAST(SUM("task_paxment"."amount") AS NUMERIC)) AS "pay",
  (CAST(((CAST(SUM("task_ixem"."amount") AS NUMERIC)) - (CAST(SUM("task_paxment"."amount") AS NUMERIC))) AS NUMERIC)) AS "bal"
FROM "task_invoixe"
LEFT OUTER JOIN "task_ixem" ON ("task_invoixe"."id" = "task_ixem"."invoixe_id")
LEFT OUTER JOIN "task_paxment" ON ("task_invoixe"."id" = "task_paxment"."invoixe_id")
GROUP BY "task_invoixe"."id", "task_invoixe"."name"
ORDER BY "task_invoixe"."id" ASC
LIMIT 1

Вот код ChatGPT считает, что этот код должен работать Я могу создать ту же ошибку, соединив менеджеры в цепочку следующим образом

>>> Invoixe.objects.with_totals().with_payments().last().tot
Decimal('60')
>>> Invoixe.objects.with_totals().last().tot
Decimal('30')

Которые также дают неправильную сумму

from django.db import models
from django.db.models import Sum, F

class InvoixeQueryset(models.QuerySet):

    def with_totals(self):
        return self.annotate(tot=Sum(F('ixem__amount')))

    def with_payments(self):
        return self.annotate(pay=Sum(F('paxment__amount')))

    def with_aggregates(self):
        return self.annotate(
            tot=Sum('ixem__amount'),
            pay=Sum('paxment__amount'),
        ).annotate(bal=F('tot') - F('pay'))

class InvoixeManager(models.Manager):
    def get_queryset(self):
        return InvoixeQueryset(self.model, using=self._db)
   
    def with_aggregates(self):
        return self.get_queryset().with_aggregates()

    def with_totals(self):
        return self.get_queryset().with_totals()
    
    def with_payments(self):
        return self.get_queryset().with_payments()
    
    def with_balance(self):
        return self.get_queryset().with_balance()


class Invoixe(models.Model):
     name = models.CharField(max_length=80, unique=True)
     objects = InvoixeManager()
     def __str__(self):
        return self.name

class Paxment(models.Model):
        amount = models.DecimalField(
            max_digits=20, decimal_places=2, default=0,
        )
        invoixe = models.ForeignKey('Invoixe', on_delete=models.CASCADE)
        def __str__(self):
            return str(self.amount)

class Ixem(models.Model):
        amount = models.DecimalField(
            max_digits=20, decimal_places=2, default=0,
        )
        invoixe = models.ForeignKey('Invoixe', on_delete=models.CASCADE)
        def __str__(self):
            return str(self.amount)

Буду очень признателен, если кто-нибудь поможет мне найти правильную реализацию. Спасибо.

Мне нужно было заставить Django использовать group-by с помощью Case и When. Также использовал одну таблицу для хранения дебетов и кредитов, в целом это выглядит лучше.

    def with_aggregates(self):
        return self.annotate(
            debit_sum=Sum(Case(When(ledger__action="DR", then=F('ledger__amount')), output_field=DecimalField())),
            credit_sum=Sum(Case(When(ledger__action="CR", then=F('ledger__amount')), output_field=DecimalField()), 
            )
        ).annotate(
            balance=(F('debit_sum') - F('credit_sum'))
        )
Вернуться на верх