Statistics on sold and unsold seats, as well as potential profi… (#1613)

* Statistics on sold and unsold seats, as well as potential profits

* Rework of seats-stats

* Fix crash when all seats are assigned

* Update src/pretix/plugins/statistics/views.py

Co-Authored-By: Raphael Michel <michel@rami.io>

* Update src/pretix/plugins/statistics/views.py

Co-Authored-By: Raphael Michel <michel@rami.io>

* Update src/pretix/plugins/statistics/views.py

Co-Authored-By: Raphael Michel <michel@rami.io>

* Fix count of sold seats

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
Martin Gross
2020-03-16 15:20:30 +01:00
committed by GitHub
parent bd238f76ce
commit f00012a63e
3 changed files with 156 additions and 5 deletions

View File

@@ -3,6 +3,8 @@
{% load compress %}
{% load static %}
{% load escapejson %}
{% load money %}
{% load getitem %}
{% block title %}{% trans "Statistics" %}{% endblock %}
{% block content %}
<h1>{% trans "Statistics" %}</h1>
@@ -59,6 +61,102 @@
<div id="obp_chart" class="chart"></div>
</div>
</div>
{% if seats %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Seating Overview" %}</h3>
</div>
<div class="panel-body">
<div class="dashboard">
<div class="widget-container widget-small">
<div class="numwidget">
<span class="num">{{ seats.purchased_seats }}</span>
<span class="text">{% trans "Sold Seats" %}</span>
</div>
</div>
<div class="widget-container widget-small">
<div class="numwidget">
<span class="num">{{ seats.blocked_seats }}</span>
<span class="text">{% trans "Blocked Seats" %}</span>
</div>
</div>
<div class="widget-container widget-small">
<div class="numwidget">
<span class="num">{{ seats.free_seats }}</span>
<span class="text">{% trans "Free Seats" %}</span>
</div>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Seating Sales Potentials" %}</h3>
</div>
<div class="panel-body">
<table class="table table-condensed table-hover table-product-overview">
<thead>
<tr>
<th>{% trans "Product" %}</th>
<th></th>
<th colspan="2" class="text-center">{% trans "Unsold Seats" %}</th>
<th colspan="2" class="text-center">{% trans "Potential Profits" %}</th>
</tr>
<tr>
<th></th>
<th>{% trans "Minimum Price" %}</th>
<th>{% trans "Blocked" %}</th>
<th>{% trans "Available" %}</th>
<th>{% trans "Blocked" %}</th>
<th>{% trans "Available" %}</th>
</tr>
</thead>
<tbody>
<tr class="category">
<th>{% trans "On Sale" %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
{% for item, props in seats.products.items %}
{% if item is not None %}
<tr class="item categorized">
<td>{{ item }}</td>
<td>{{ props.price|money:request.event.currency }}</td>
<td>{{ props.blocked.seats }}</td>
<td>{{ props.free.seats }}</td>
<td>{{ props.blocked.potential|money:request.event.currency }}</td>
<td>{{ props.free.potential|money:request.event.currency }}</td>
</tr>
{% endif %}
{% endfor %}
<tr class="category">
<th>{% trans "Not on Sale" %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
{% if None in seats.products %}
{% with seats.products|getitem:None as unattributed %}
<tr class="item categorized">
<td>{% trans "Seats not attributed to any specific product" %}</td>
<td></td>
<td>{{ unattributed.blocked.seats }}</td>
<td>{{ unattributed.free.seats }}</td>
<td></td>
<td></td>
</tr>
{% endwith %}
{% endif %}
</tbody>
</table>
</div>
</div>
{% endif %}
<script type="application/json" id="obd-data">{{ obd_data|escapejson }}</script>
<script type="application/json" id="rev-data">{{ rev_data|escapejson }}</script>
<script type="application/json" id="obp-data">{{ obp_data|escapejson }}</script>

View File

@@ -1,9 +1,10 @@
import datetime
import json
from decimal import Decimal
import dateutil.parser
import dateutil.rrule
from django.db.models import Count, DateTimeField, Max, OuterRef, Subquery
from django.db.models import Count, DateTimeField, Max, Min, OuterRef, Subquery
from django.utils import timezone
from django.views.generic import TemplateView
@@ -162,4 +163,56 @@ class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView)
ctx['has_orders'] = self.request.event.orders.exists()
ctx['seats'] = {}
if not self.request.event.has_subevents or (ckey != "all" and subevent):
ev = subevent or self.request.event
if ev.seating_plan_id is not None:
seats_qs = ev.free_seats(sales_channel=None, include_blocked=True)
ctx['seats']['blocked_seats'] = seats_qs.filter(blocked=True).count()
ctx['seats']['free_seats'] = seats_qs.filter(blocked=False).count()
ctx['seats']['purchased_seats'] = \
ev.seats.count() - ctx['seats']['blocked_seats'] - ctx['seats']['free_seats']
seats_qs = seats_qs.values('product', 'blocked').annotate(count=Count('id'))\
.order_by('product__category__position', 'product__position', 'product', 'blocked')
ctx['seats']['products'] = {}
ctx['seats']['stats'] = {}
item_cache = {i.pk: i for i in
ev.items.annotate(has_variations=Count('variations')).filter(
pk__in={p['product'] for p in seats_qs if p['product']}
)}
item_cache[None] = None
for item in seats_qs:
if item_cache[item['product']] not in ctx['seats']['products']:
product = item_cache[item['product']]
if product and product.has_variations:
price = product.variations.aggregate(Min('default_price'))['default_price__min']
elif product:
price = product.default_price
else:
price = Decimal('0.00')
ctx['seats']['products'][product] = {
'free': {
'seats': 0,
'potential': Decimal('0.00'),
},
'blocked': {
'seats': 0,
'potential': Decimal('0.00'),
},
'price': price,
}
data = ctx['seats']['products'][product]
if item['blocked']:
data['blocked']['seats'] = item['count']
data['blocked']['potential'] = item['count'] * data['price']
else:
data['free']['seats'] = item['count']
data['free']['potential'] = item['count'] * data['price']
return ctx