Merge branch 'main' into storefront-nuxt

This commit is contained in:
Egor Pavlovich Gorbunov 2025-11-17 10:03:53 +03:00
commit 292b26acce
4 changed files with 37 additions and 23 deletions

View file

@ -27,7 +27,7 @@
{% trans "Revenue (gross, 30d)" %}
{% endcomponent %}
{% component "unfold/components/title.html" %}
{{ revenue_gross_30|default:0 }}
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ revenue_gross_30|default:0 }}
{% endcomponent %}
{% endcomponent %}
@ -36,7 +36,7 @@
{% trans "Revenue (net, 30d)" %}
{% endcomponent %}
{% component "unfold/components/title.html" %}
{{ revenue_net_30|default:0 }}
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ revenue_net_30|default:0 }}
{% endcomponent %}
{% endcomponent %}
@ -45,7 +45,7 @@
{% trans "Returns (30d)" %}
{% endcomponent %}
{% component "unfold/components/title.html" %}
{{ returns_30|default:0 }}
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ returns_30|default:0 }}
{% endcomponent %}
{% endcomponent %}
@ -83,13 +83,13 @@
<span class="inline-block w-3 h-3 rounded-sm"
style="background:rgb(34,197,94)"></span>
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Gross" %}:</span>
<span class="font-semibold">{{ gross }}</span>
<span class="font-semibold">{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ gross }}</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block w-3 h-3 rounded-sm"
style="background:rgb(239,68,68)"></span>
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Returns" %}:</span>
<span class="font-semibold">{{ returns }}</span>
<span class="font-semibold">{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ returns }}</span>
</div>
</div>
</div>

View file

@ -1,13 +1,16 @@
from datetime import timedelta
from django.db.models import F, Sum
from constance import config
from django.db.models import F, QuerySet, Sum
from django.db.models.functions import Coalesce
from django.utils.timezone import now
from constance import config
from engine.core.models import Order, OrderProduct
def get_period_order_products(period: timedelta = timedelta(days=30), statuses: list[str] | None = None):
def get_period_order_products(
period: timedelta = timedelta(days=30), statuses: list[str] | None = None
) -> QuerySet[OrderProduct]:
if statuses is None:
statuses = ["FINISHED"]
current = now()
@ -16,7 +19,7 @@ def get_period_order_products(period: timedelta = timedelta(days=30), statuses:
return OrderProduct.objects.filter(status__in=statuses, order__in=orders)
def get_revenue(clear: bool = True, period: timedelta = timedelta(days=30)):
def get_revenue(clear: bool = True, period: timedelta = timedelta(days=30)) -> float:
order_products = get_period_order_products(period)
total: float = (
order_products.aggregate(total=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0)).get("total") or 0.0
@ -37,7 +40,7 @@ def get_revenue(clear: bool = True, period: timedelta = timedelta(days=30)):
return round(float(total), 2)
def get_returns(period: timedelta = timedelta(days=30)):
def get_returns(period: timedelta = timedelta(days=30)) -> float:
order_products = get_period_order_products(period, ["RETURNED"])
total_returns: float = (
order_products.aggregate(total=Coalesce(Sum(F("buy_price") * F("quantity")), 0.0)).get("total") or 0.0
@ -48,5 +51,5 @@ def get_returns(period: timedelta = timedelta(days=30)):
return 0.0
def get_total_processed_orders(period: timedelta = timedelta(days=30)):
def get_total_processed_orders(period: timedelta = timedelta(days=30)) -> int:
return get_period_order_products(period, ["RETURNED", "FINISHED"]).count()

View file

@ -13,6 +13,7 @@ from django.core.exceptions import BadRequest
from django.db.models import Count, Sum
from django.http import FileResponse, Http404, HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect
from django.template import Context
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import urlsafe_base64_decode
@ -416,11 +417,16 @@ version.__doc__ = _( # type: ignore [assignment]
)
def dashboard_callback(request, context):
revenue_gross_30 = get_revenue(clear=False)
revenue_net_30 = get_revenue(clear=True)
returns_30 = get_returns()
processed_orders_30 = get_total_processed_orders()
def dashboard_callback(request: HttpRequest, context: Context) -> Context:
revenue_gross_30: float = get_revenue(clear=False)
revenue_net_30: float = get_revenue(clear=True)
returns_30: float = get_returns()
processed_orders_30: int = get_total_processed_orders()
currency_symbol: str = ""
with suppress(Exception):
currency_symbol = dict(getattr(settings, "CURRENCIES_WITH_SYMBOLS", ())).get(
getattr(settings, "CURRENCY_CODE", ""), ""
)
quick_links: list[dict[str, str]] = []
with suppress(Exception):
@ -454,7 +460,7 @@ def dashboard_callback(request, context):
if wished_first and wished_first.get("products"):
product = Product.objects.filter(pk=wished_first["products"]).first()
if product:
img = product.images.first().image_url if product.images.exists() else ""
img = product.images.first().image_url if product.images.exists() else "" # type: ignore [union-attr]
most_wished = {
"name": product.name,
"image": img,
@ -473,7 +479,7 @@ def dashboard_callback(request, context):
if not pid or pid not in product_by_id:
continue
p = product_by_id[pid]
img = p.images.first().image_url if p.images.exists() else ""
img = p.images.first().image_url if p.images.exists() else "" # type: ignore [union-attr]
most_wished_list.append(
{
"name": p.name,
@ -498,7 +504,7 @@ def dashboard_callback(request, context):
if popular_first and popular_first.get("product"):
product = Product.objects.filter(pk=popular_first["product"]).first()
if product:
img = product.images.first().image_url if product.images.exists() else ""
img = product.images.first().image_url if product.images.exists() else "" # type: ignore [union-attr]
most_popular = {
"name": product.name,
"image": img,
@ -515,7 +521,7 @@ def dashboard_callback(request, context):
if not pid or pid not in product_by_id:
continue
p = product_by_id[pid]
img = p.images.first().image_url if p.images.exists() else ""
img = p.images.first().image_url if p.images.exists() else "" # type: ignore [union-attr]
most_popular_list.append(
{
"name": p.name,
@ -538,6 +544,7 @@ def dashboard_callback(request, context):
"most_popular_product": most_popular,
"most_wished_products": most_wished_list,
"most_popular_products": most_popular_list,
"currency_symbol": currency_symbol,
}
)

View file

@ -1,17 +1,21 @@
from typing import Any
from django.templatetags.static import static
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from evibes.settings.base import (
LANGUAGES as BASE_LANGUAGES,
)
from evibes.settings.base import (
LANGUAGES_FLAGS,
PROJECT_NAME,
STOREFRONT_DOMAIN,
SUPPORT_CONTACT,
TASKBOARD_URL,
LANGUAGES as BASE_LANGUAGES,
LANGUAGES_FLAGS,
)
UNFOLD = {
UNFOLD: dict[str, Any] = {
"SITE_URL": STOREFRONT_DOMAIN,
"SITE_TITLE": f"{PROJECT_NAME} Dashboard",
"SITE_HEADER": PROJECT_NAME,