add detailed ticket stats view

This commit is contained in:
Thomas Steen Rasmussen 2021-07-21 21:29:55 +02:00
parent a8c4894b8f
commit 5c410939ed
5 changed files with 80 additions and 4 deletions

View file

@ -17,15 +17,17 @@
<th class="text-center">Number Sold</th> <th class="text-center">Number Sold</th>
<th class="text-right">Total Income</th> <th class="text-right">Total Income</th>
<th class="text-right">Average Price</th> <th class="text-right">Average Price</th>
<th class="text-center">Products</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for tt in tickettype_list %} {% for tt in tickettype_list %}
<tr> <tr>
<td>{{ tt.name }}</td> <td><a href="{% url 'backoffice:shop_ticket_stats_detail' camp_slug=camp.slug pk=tt.pk %}">{{ tt.name }}</a></td>
<td class="text-center">{{ tt.shopticket_count }}</td> <td class="text-center">{{ tt.shopticket_count }}</td>
<td class="text-right">{{ tt.total_price|floatformat:"2" }} DKK</td> <td class="text-right">{{ tt.total_price|floatformat:"2" }} DKK</td>
<td class="text-right">{{ tt.average_price|floatformat:"2" }} DKK</td> <td class="text-right">{{ tt.average_price|floatformat:"2" }} DKK</td>
<td class="text-center">{{ tt.product_set.count }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load commonmark %}
{% load static %}
{% load bornhack %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<span class="h3">BackOffice - {{ camp.title }} {{ product_list.0.ticket_type.name }} Ticket Product Stats</span>
</div>
<div class="panel-body">
<p class="lead">This view shows a table of each product associated with the ticket type <b>{{ product_list.0.ticket_type.name }}</b> along with the number sold, cost, price, profit and margin for each product.</p>
<table class="table table-hover datatable">
<thead>
<tr>
<th>Product</th>
<th class="text-center">Number Sold</th>
<th class="text-right">Revenue</th>
<th class="text-right">Cost</th>
<th class="text-right">Profit</th>
<th class="text-right">Margin</th>
</tr>
</thead>
<tbody>
{% for p in product_list %}
<tr>
<td>{{ p.name }}</td>
<td class="text-center">{{ p.ticket_count }}</td>
<td class="text-right">{{ p.price|floatformat:"2" }} DKK</td>
<td class="text-right">{{ p.cost|floatformat:"2" }} DKK</td>
<td class="text-right">{{ p.profit|floatformat:"2" }} DKK</td>
<td class="text-right">{{ p.margin }}%</td>
</tr>
{% endfor %}
</tbody>
</table>
<a class="btn btn-default" href="{% url 'backoffice:shop_ticket_stats' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Ticket Typess</a>
</p>
</div>
</div>
{% endblock content %}

View file

@ -85,6 +85,7 @@ from .views import (
RevenueListView, RevenueListView,
ScanTicketsView, ScanTicketsView,
ShopTicketOverview, ShopTicketOverview,
ShopTicketStatsDetailView,
ShopTicketStatsView, ShopTicketStatsView,
SpeakerDeleteView, SpeakerDeleteView,
SpeakerDetailView, SpeakerDetailView,
@ -811,6 +812,15 @@ urlpatterns = [
), ),
path( path(
"shop_ticket_stats/", "shop_ticket_stats/",
include([path("", ShopTicketStatsView.as_view(), name="shop_ticket_stats")]), include(
[
path("", ShopTicketStatsView.as_view(), name="shop_ticket_stats"),
path(
"<uuid:pk>/",
ShopTicketStatsDetailView.as_view(),
name="shop_ticket_stats_detail",
),
]
),
), ),
] ]

View file

@ -10,7 +10,7 @@ from django.views.generic.edit import FormView
from camps.mixins import CampViewMixin from camps.mixins import CampViewMixin
from profiles.models import Profile from profiles.models import Profile
from shop.models import OrderProductRelation from shop.models import OrderProductRelation, Product
from teams.models import Team from teams.models import Team
from tickets.models import TicketType from tickets.models import TicketType
from utils.models import OutgoingEmail from utils.models import OutgoingEmail
@ -204,3 +204,13 @@ class ShopTicketStatsView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
def get_queryset(self): def get_queryset(self):
return TicketType.objects.with_price_stats().filter(camp=self.camp) 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"]
)

View file

@ -7,7 +7,7 @@ from django.contrib import messages
from django.contrib.postgres.fields import DateTimeRangeField from django.contrib.postgres.fields import DateTimeRangeField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models 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.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
@ -394,6 +394,11 @@ class ProductCategory(CreatedUpdatedModel, UUIDModel):
super().save(**kwargs) 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 Product(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
verbose_name = "Product" verbose_name = "Product"
@ -443,6 +448,7 @@ class Product(CreatedUpdatedModel, UUIDModel):
) )
objects = ProductQuerySet.as_manager() objects = ProductQuerySet.as_manager()
statsobjects = ProductStatsManager()
def __str__(self): def __str__(self):
return "{} ({} DKK)".format(self.name, self.price) return "{} ({} DKK)".format(self.name, self.price)
@ -513,6 +519,13 @@ class Product(CreatedUpdatedModel, UUIDModel):
except ValueError: except ValueError:
return 0 return 0
@property
def margin(self):
try:
return (self.price / self.profit) * 100
except ValueError:
return 0
class OrderProductRelation(CreatedUpdatedModel): class OrderProductRelation(CreatedUpdatedModel):
order = models.ForeignKey("shop.Order", on_delete=models.PROTECT) order = models.ForeignKey("shop.Order", on_delete=models.PROTECT)