mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user