add detailed ticket stats view
This commit is contained in:
parent
a8c4894b8f
commit
5c410939ed
|
@ -17,15 +17,17 @@
|
|||
<th class="text-center">Number Sold</th>
|
||||
<th class="text-right">Total Income</th>
|
||||
<th class="text-right">Average Price</th>
|
||||
<th class="text-center">Products</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tt in tickettype_list %}
|
||||
<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-right">{{ tt.total_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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
41
src/backoffice/templates/ticket_stats_detail.html
Normal file
41
src/backoffice/templates/ticket_stats_detail.html
Normal 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 %}
|
|
@ -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(
|
||||
"<uuid:pk>/",
|
||||
ShopTicketStatsDetailView.as_view(),
|
||||
name="shop_ticket_stats_detail",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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"]
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue