Merge pull request #859 from bornhack/add_ticket_stats_on_overview_take_two
Ticket stats take two
This commit is contained in:
commit
db7d4909c7
|
@ -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" }} DKK</td>
|
<td class="text-right">{{ tt.total_income|floatformat:"2" }} DKK</td>
|
||||||
<td class="text-right">{{ tt.total_cost|floatformat:"2" }} DKK</td>
|
<td class="text-right">{{ tt.total_cost|floatformat:"2" }} DKK</td>
|
||||||
<td class="text-right">{{ tt.total_profit|floatformat:"2" }} DKK</td>
|
<td class="text-right">{{ tt.total_profit|floatformat:"2" }} DKK</td>
|
||||||
|
<td class="text-right">{{ tt.avg_ticket_price|floatformat:"2" }} DKK</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 (
|
|
||||||
self.filter(shopticket__isnull=False)
|
|
||||||
.annotate(shopticket_count=Count("shopticket"))
|
|
||||||
.annotate(total_units_sold=total_units_sold)
|
|
||||||
.annotate(total_income=total_income)
|
|
||||||
.annotate(total_cost=total_cost)
|
|
||||||
.annotate(total_profit=total_profit)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
quantity = F("product__orderproductrelation__quantity")
|
||||||
|
cost = quantity * F("product__cost")
|
||||||
|
income = quantity * F("product__price")
|
||||||
|
|
||||||
|
avg_ticket_price = Subquery(
|
||||||
|
TicketType.objects.annotate(units=Sum(quantity))
|
||||||
|
.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)
|
||||||
|
|
Loading…
Reference in a new issue