Compare commits

..

2 commits

Author SHA1 Message Date
51e9418e54 Merge branch 'master' into storefront-nuxt 2026-03-03 13:49:55 +03:00
aabd8b18b4 feat(commerce): ensure precise decimal handling in aggregated calculations
replace float-based defaults with `Decimal` in Coalesce and set `DecimalField` as the output type to improve precision when aggregating monetary values.
2026-03-03 13:49:29 +03:00

View file

@ -1,9 +1,10 @@
from contextlib import suppress
from datetime import date, timedelta
from decimal import Decimal
from typing import Any
from constance import config
from django.db.models import Count, F, QuerySet, Sum
from django.db.models import Count, DecimalField, F, QuerySet, Sum
from django.db.models.functions import Coalesce, TruncDate
from django.urls import reverse
from django.utils.timezone import now
@ -28,7 +29,11 @@ def get_revenue(clear: bool = True, period: timedelta = timedelta(days=30)) -> f
order_products = get_period_order_products(period)
total: float = (
order_products.aggregate(
total=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0)
total=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
)
).get("total")
or 0.0
)
@ -78,7 +83,13 @@ def get_returns(period: timedelta = timedelta(days=30)) -> float:
order__buy_time__lte=current,
order__buy_time__gte=period_start,
)
.aggregate(total=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0))
.aggregate(
total=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
)
)
.get("total")
or 0.0
)
@ -122,7 +133,13 @@ def get_daily_gross_revenue(
get_period_order_products(period, ["FINISHED"]) # OrderProduct queryset
.annotate(day=TruncDate("order__buy_time"))
.values("day")
.annotate(total=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0))
.annotate(
total=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
)
)
.order_by("day")
)
result: dict[date, float] = {}
@ -158,7 +175,11 @@ def get_top_returned_products(
.values("product")
.annotate(
returned_qty=Coalesce(Sum("quantity"), 0),
returned_amount=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0),
returned_amount=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
),
)
.order_by("-returned_qty")[:limit]
)
@ -236,7 +257,11 @@ def get_top_categories_by_qty(
.values("product__category")
.annotate(
qty=Coalesce(Sum("quantity"), 0),
gross=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0),
gross=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
),
)
.order_by("-qty", "-gross")[:limit]
)
@ -277,7 +302,11 @@ def get_shipped_vs_digital_mix(
.values("product__is_digital")
.annotate(
qty=Coalesce(Sum("quantity"), 0),
gross=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0),
gross=Coalesce(
Sum(F("buy_price") * F("quantity")),
Decimal("0.00"),
output_field=DecimalField(),
),
)
)