diff --git a/src/backoffice/templates/ticket_stats.html b/src/backoffice/templates/ticket_stats.html index e0cc2540..ca2715a1 100644 --- a/src/backoffice/templates/ticket_stats.html +++ b/src/backoffice/templates/ticket_stats.html @@ -17,15 +17,17 @@ Number Sold Total Income Average Price + Products {% for tt in tickettype_list %} - {{ tt.name }} + {{ tt.name }} {{ tt.shopticket_count }} {{ tt.total_price|floatformat:"2" }} DKK {{ tt.average_price|floatformat:"2" }} DKK + {{ tt.product_set.count }} {% endfor %} diff --git a/src/backoffice/templates/ticket_stats_detail.html b/src/backoffice/templates/ticket_stats_detail.html new file mode 100644 index 00000000..ae8f27ad --- /dev/null +++ b/src/backoffice/templates/ticket_stats_detail.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static %} +{% load bornhack %} + +{% block content %} +
+
+ BackOffice - {{ camp.title }} {{ product_list.0.ticket_type.name }} Ticket Product Stats +
+
+

This view shows a table of each product associated with the ticket type {{ product_list.0.ticket_type.name }} along with the number sold, cost, price, profit and margin for each product.

+ + + + + + + + + + + + + {% for p in product_list %} + + + + + + + + + {% endfor %} + +
ProductNumber SoldRevenueCostProfitMargin
{{ p.name }}{{ p.ticket_count }}{{ p.price|floatformat:"2" }} DKK{{ p.cost|floatformat:"2" }} DKK{{ p.profit|floatformat:"2" }} DKK{{ p.margin }}%
+ Back to Ticket Typess +

+
+
+{% endblock content %} diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py index 62dd3091..a669d26f 100644 --- a/src/backoffice/urls.py +++ b/src/backoffice/urls.py @@ -85,6 +85,7 @@ from .views import ( RevenueListView, ScanTicketsView, ShopTicketOverview, + ShopTicketStatsDetailView, ShopTicketStatsView, SpeakerDeleteView, SpeakerDetailView, @@ -811,6 +812,15 @@ urlpatterns = [ ), path( "shop_ticket_stats/", - include([path("", ShopTicketStatsView.as_view(), name="shop_ticket_stats")]), + include( + [ + path("", ShopTicketStatsView.as_view(), name="shop_ticket_stats"), + path( + "/", + ShopTicketStatsDetailView.as_view(), + name="shop_ticket_stats_detail", + ), + ] + ), ), ] diff --git a/src/backoffice/views/orga.py b/src/backoffice/views/orga.py index 0c402fd5..375f99e0 100644 --- a/src/backoffice/views/orga.py +++ b/src/backoffice/views/orga.py @@ -10,7 +10,7 @@ from django.views.generic.edit import FormView from camps.mixins import CampViewMixin from profiles.models import Profile -from shop.models import OrderProductRelation +from shop.models import OrderProductRelation, Product from teams.models import Team from tickets.models import TicketType from utils.models import OutgoingEmail @@ -204,3 +204,13 @@ class ShopTicketStatsView(CampViewMixin, OrgaTeamPermissionMixin, ListView): def get_queryset(self): return TicketType.objects.with_price_stats().filter(camp=self.camp) + + +class ShopTicketStatsDetailView(CampViewMixin, OrgaTeamPermissionMixin, ListView): + model = Product + template_name = "ticket_stats_detail.html" + + def get_queryset(self): + return Product.statsobjects.with_ticket_stats().filter( + ticket_type_id=self.kwargs["pk"] + ) diff --git a/src/shop/models.py b/src/shop/models.py index 211df9d8..e9a07055 100644 --- a/src/shop/models.py +++ b/src/shop/models.py @@ -7,7 +7,7 @@ from django.contrib import messages from django.contrib.postgres.fields import DateTimeRangeField from django.core.exceptions import ValidationError from django.db import models -from django.db.models.aggregates import Sum +from django.db.models.aggregates import Count, Sum from django.urls import reverse_lazy from django.utils import timezone from django.utils.dateparse import parse_datetime @@ -394,6 +394,11 @@ class ProductCategory(CreatedUpdatedModel, UUIDModel): super().save(**kwargs) +class ProductStatsManager(models.Manager): + def with_ticket_stats(self): + return self.annotate(ticket_count=Count("shopticket")).exclude(ticket_count=0) + + class Product(CreatedUpdatedModel, UUIDModel): class Meta: verbose_name = "Product" @@ -443,6 +448,7 @@ class Product(CreatedUpdatedModel, UUIDModel): ) objects = ProductQuerySet.as_manager() + statsobjects = ProductStatsManager() def __str__(self): return "{} ({} DKK)".format(self.name, self.price) @@ -513,6 +519,13 @@ class Product(CreatedUpdatedModel, UUIDModel): except ValueError: return 0 + @property + def margin(self): + try: + return (self.price / self.profit) * 100 + except ValueError: + return 0 + class OrderProductRelation(CreatedUpdatedModel): order = models.ForeignKey("shop.Order", on_delete=models.PROTECT)