Merge pull request #859 from bornhack/add_ticket_stats_on_overview_take_two

Ticket stats take two
This commit is contained in:
Thomas Steen Rasmussen 2021-07-30 07:28:33 +02:00 committed by GitHub
commit db7d4909c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 18 deletions

View File

@ -20,6 +20,7 @@
<th class="text-right">Total Income</th> <th class="text-right">Total Income</th>
<th class="text-right">Total Cost</th> <th class="text-right">Total Cost</th>
<th class="text-right">Total Profit</th> <th class="text-right">Total Profit</th>
<th class="text-right">Avg. Ticket Price</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -32,6 +33,7 @@
<td class="text-right">{{ tt.total_income|floatformat:"2" }}&nbsp;DKK</td> <td class="text-right">{{ tt.total_income|floatformat:"2" }}&nbsp;DKK</td>
<td class="text-right">{{ tt.total_cost|floatformat:"2" }}&nbsp;DKK</td> <td class="text-right">{{ tt.total_cost|floatformat:"2" }}&nbsp;DKK</td>
<td class="text-right">{{ tt.total_profit|floatformat:"2" }}&nbsp;DKK</td> <td class="text-right">{{ tt.total_profit|floatformat:"2" }}&nbsp;DKK</td>
<td class="text-right">{{ tt.avg_ticket_price|floatformat:"2" }}&nbsp;DKK</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -203,7 +203,10 @@ class ShopTicketStatsView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
template_name = "ticket_stats.html" template_name = "ticket_stats.html"
def get_queryset(self): def get_queryset(self):
return TicketType.objects.with_price_stats().filter(camp=self.camp) query = TicketType.objects.filter(
camp=self.camp, shopticket__isnull=False
).with_price_stats()
return query
class ShopTicketStatsDetailView(CampViewMixin, OrgaTeamPermissionMixin, ListView): class ShopTicketStatsDetailView(CampViewMixin, OrgaTeamPermissionMixin, ListView):

View File

@ -2,11 +2,21 @@ import base64
import hashlib import hashlib
import io import io
import logging import logging
from decimal import Decimal
from typing import Union
import qrcode import qrcode
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models import Count, F, Sum from django.db.models import (
Count,
Expression,
ExpressionWrapper,
F,
OuterRef,
Subquery,
Sum,
)
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -16,29 +26,46 @@ from utils.pdf import generate_pdf_letter
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
class TicketTypeManager(models.Manager): class TicketTypeQuerySet(models.QuerySet):
def with_price_stats(self): def with_price_stats(self):
total_units_sold = Sum("shopticket__opr__quantity", distinct=True) def _make_subquery(annotation: Union[Expression, F]) -> Subquery:
cost = F("shopticket__opr__quantity") * F("shopticket__opr__product__cost") return Subquery(
income = F("shopticket__opr__quantity") * F("shopticket__opr__product__price") TicketType.objects.annotate(annotation_value=annotation)
profit = income - cost .filter(pk=OuterRef("pk"))
total_cost = Sum(cost, distinct=True) .values("annotation_value")[:1]
total_profit = Sum(profit, distinct=True) )
total_income = Sum(income, distinct=True)
return ( quantity = F("product__orderproductrelation__quantity")
self.filter(shopticket__isnull=False) cost = quantity * F("product__cost")
.annotate(shopticket_count=Count("shopticket")) income = quantity * F("product__price")
.annotate(total_units_sold=total_units_sold)
.annotate(total_income=total_income) avg_ticket_price = Subquery(
.annotate(total_cost=total_cost) TicketType.objects.annotate(units=Sum(quantity))
.annotate(total_profit=total_profit) .annotate(income=Sum(income))
.annotate(
avg_ticket_price=ExpressionWrapper(
F("income") * Decimal("1.0") / F("units"),
output_field=models.DecimalField(),
)
)
.filter(pk=OuterRef("pk"))
.values("avg_ticket_price")[:1],
output_field=models.DecimalField(),
) )
return self.annotate(
shopticket_count=_make_subquery(Count("shopticket")),
total_units_sold=_make_subquery(quantity),
total_income=_make_subquery(income),
total_cost=_make_subquery(cost),
total_profit=_make_subquery(income - cost),
avg_ticket_price=avg_ticket_price,
).distinct()
# TicketType can be full week, one day, cabins, parking, merch, hax, massage, etc. # TicketType can be full week, one day, cabins, parking, merch, hax, massage, etc.
class TicketType(CampRelatedModel, UUIDModel): class TicketType(CampRelatedModel, UUIDModel):
objects = TicketTypeManager() objects = TicketTypeQuerySet.as_manager()
name = models.TextField() name = models.TextField()
camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT) camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT)
includes_badge = models.BooleanField(default=False) includes_badge = models.BooleanField(default=False)