from collections import defaultdict from datetime import datetime from decimal import Decimal from django.db.models import ( Case, CharField, Count, DecimalField, ExpressionWrapper, F, OuterRef, Q, Subquery, Sum, Value, When, ) from django.db.models.functions import Coalesce from commerce.models import Cost, Product, SaleEventType, SaleLine, StockSnapshot MONEY_FIELD = DecimalField(max_digits=20, decimal_places=2) ZERO_MONEY = Decimal("0.00") def _date_filter(prefix: str, date_from: datetime | None, date_to: datetime | None): query = Q() if date_from is not None: query &= Q(**{f"{prefix}__gte": date_from}) if date_to is not None: query &= Q(**{f"{prefix}__lte": date_to}) return query def _sale_metric_rows( product: Product, date_from: datetime | None, date_to: datetime | None, *group_fields: str, ): signed_quantity = ExpressionWrapper( F("quantity") * models_case_return_multiplier(), output_field=DecimalField(max_digits=20, decimal_places=4), ) signed_gross = ExpressionWrapper( F("gross_amount") * models_case_return_multiplier(), output_field=MONEY_FIELD ) signed_discount = ExpressionWrapper( F("discount_amount") * models_case_return_multiplier(), output_field=MONEY_FIELD, ) signed_cost = ExpressionWrapper( F("cost_amount") * models_case_return_multiplier(), output_field=MONEY_FIELD ) rows = ( SaleLine.objects.filter(product=product, sale__counts_as_sale=True) .filter(_date_filter("sale__occurred_at", date_from, date_to)) .values(*group_fields) .annotate( quantity=Coalesce(Sum(signed_quantity), Decimal("0")), gross=Coalesce(Sum(signed_gross), ZERO_MONEY), discounts=Coalesce(Sum(signed_discount), ZERO_MONEY), cost_of_goods=Coalesce(Sum(signed_cost), ZERO_MONEY), missing_cost_lines=Count( "id", filter=Q( metadata__cost_source="unavailable", ), ), ) .order_by(*group_fields) ) return list(rows) def _sale_metrics( product: Product, date_from: datetime | None, date_to: datetime | None, ): rows = _sale_metric_rows(product, date_from, date_to, "sale__currency") return {row["sale__currency"]: row for row in rows} def models_case_return_multiplier(): from django.db.models import Case, IntegerField, Value, When return Case( When(sale__event_type=SaleEventType.RETURN, then=Value(-1)), default=Value(1), output_field=IntegerField(), ) def _cost_metrics( product: Product, date_from: datetime | None, date_to: datetime | None, ): rows = ( Cost.objects.filter(Q(product=product) | Q(sale_line__product=product)) .filter(_date_filter("occurred_at", date_from, date_to)) .values("currency", "cost_type") .annotate(amount=Coalesce(Sum("amount"), ZERO_MONEY)) .order_by("currency", "cost_type") ) result: dict[str, dict[str, Decimal]] = defaultdict(dict) for row in rows: result[row["currency"]][row["cost_type"]] = row["amount"] return result def _channel_metrics( product: Product, date_from: datetime | None, date_to: datetime | None, ): sale_rows = _sale_metric_rows( product, date_from, date_to, "sale__channel", "sale__currency", ) sales = {(row["sale__channel"], row["sale__currency"]): row for row in sale_rows} cost_rows = ( Cost.objects.filter(Q(product=product) | Q(sale_line__product=product)) .filter(_date_filter("occurred_at", date_from, date_to)) .annotate( effective_channel=Case( When( channel="", then=Coalesce("sale_line__sale__channel", Value("")), ), default=F("channel"), output_field=CharField(), ) ) .values("effective_channel", "currency", "cost_type") .annotate(amount=Coalesce(Sum("amount"), ZERO_MONEY)) .order_by("effective_channel", "currency", "cost_type") ) costs: dict[tuple[str, str], dict[str, Decimal]] = defaultdict(dict) for row in cost_rows: costs[(row["effective_channel"], row["currency"])][row["cost_type"]] = row[ "amount" ] result = [] for channel, currency in sorted(set(sales) | set(costs)): sale = sales.get((channel, currency), {}) gross = sale.get("gross", ZERO_MONEY) discounts = sale.get("discounts", ZERO_MONEY) cost_of_goods = sale.get("cost_of_goods", ZERO_MONEY) expense_breakdown = costs.get((channel, currency), {}) additional_costs = sum(expense_breakdown.values(), ZERO_MONEY) net_revenue = gross - discounts result.append( { "channel": channel or "unattributed", "currency": currency, "quantity": sale.get("quantity", Decimal("0")), "net_revenue": net_revenue, "cost_of_goods": cost_of_goods, "additional_costs": additional_costs, "contribution_margin": net_revenue - cost_of_goods - additional_costs, "cost_breakdown": expense_breakdown, "missing_cost_lines": sale.get("missing_cost_lines", 0), "cost_complete": sale.get("missing_cost_lines", 0) == 0, } ) return result def _latest_stock(product: Product): latest_pk = ( StockSnapshot.objects.filter( product=product, source_id=OuterRef("source_id"), warehouse_external_id=OuterRef("warehouse_external_id"), ) .order_by("-captured_at", "-created_at") .values("pk")[:1] ) snapshots = ( StockSnapshot.objects.filter(product=product, pk=Subquery(latest_pk)) .select_related("source") .order_by("source__code", "warehouse_external_id") ) return [ { "source": snapshot.source.code, "warehouse_external_id": snapshot.warehouse_external_id, "warehouse_name": snapshot.warehouse_name, "quantity": snapshot.quantity, "reserved": snapshot.reserved, "available": snapshot.available, "in_transit": snapshot.in_transit, "captured_at": snapshot.captured_at, } for snapshot in snapshots ] def get_product_metrics( product: Product, *, date_from: datetime | None = None, date_to: datetime | None = None, ): sales = _sale_metrics(product, date_from, date_to) costs = _cost_metrics(product, date_from, date_to) currencies = sorted(set(sales) | set(costs)) monetary = [] for currency in currencies: sale = sales.get(currency, {}) gross = sale.get("gross", ZERO_MONEY) discounts = sale.get("discounts", ZERO_MONEY) cost_of_goods = sale.get("cost_of_goods", ZERO_MONEY) expense_breakdown = costs.get(currency, {}) additional_costs = sum(expense_breakdown.values(), ZERO_MONEY) net_revenue = gross - discounts monetary.append( { "currency": currency, "quantity": sale.get("quantity", Decimal("0")), "gross_revenue": gross, "discounts": discounts, "net_revenue": net_revenue, "cost_of_goods": cost_of_goods, "additional_costs": additional_costs, "contribution_margin": net_revenue - cost_of_goods - additional_costs, "cost_breakdown": expense_breakdown, "missing_cost_lines": sale.get("missing_cost_lines", 0), "cost_complete": sale.get("missing_cost_lines", 0) == 0, } ) return { "product_id": product.id, "period": {"from": date_from, "to": date_to}, "monetary": monetary, "by_channel": _channel_metrics(product, date_from, date_to), "stock": _latest_stock(product), }