forked from CGM_Public/pretix_original
Add daily and cumulative attendee graphs to the order statistics page (#5792)
The order statistics page previously only showed order-based graphs. This change adds attendee-based daily and cumulative graphs.
This commit is contained in:
@@ -19,6 +19,30 @@ $(function () {
|
|||||||
fillOpacity: 0.3,
|
fillOpacity: 0.3,
|
||||||
behaveLikeLine: true
|
behaveLikeLine: true
|
||||||
});
|
});
|
||||||
|
new Morris.Area({
|
||||||
|
element: 'abd_chart',
|
||||||
|
data: JSON.parse($("#abd-data").html()),
|
||||||
|
xkey: 'date',
|
||||||
|
ykeys: ['ordered', 'paid'],
|
||||||
|
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||||
|
lineColors: ['#3b1c4a', '#50a167'],
|
||||||
|
smooth: false,
|
||||||
|
resize: true,
|
||||||
|
fillOpacity: 0.3,
|
||||||
|
behaveLikeLine: true
|
||||||
|
});
|
||||||
|
new Morris.Area({
|
||||||
|
element: 'abt_chart',
|
||||||
|
data: JSON.parse($("#abt-data").html()),
|
||||||
|
xkey: 'date',
|
||||||
|
ykeys: ['ordered', 'paid'],
|
||||||
|
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||||
|
lineColors: ['#3b1c4a', '#50a167'],
|
||||||
|
smooth: false,
|
||||||
|
resize: true,
|
||||||
|
fillOpacity: 0.3,
|
||||||
|
behaveLikeLine: true
|
||||||
|
});
|
||||||
new Morris.Area({
|
new Morris.Area({
|
||||||
element: 'rev_chart',
|
element: 'rev_chart',
|
||||||
data: JSON.parse($("#rev-data").html()),
|
data: JSON.parse($("#rev-data").html()),
|
||||||
|
|||||||
@@ -31,6 +31,46 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{% trans "Attendees by day" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div id="abd_chart" class="chart"></div>
|
||||||
|
<p class="help-block">
|
||||||
|
<small>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Attendees in orders paid in multiple instalments are shown using the date of the
|
||||||
|
final payment. Order dates reflect when the order was first placed; attendees added
|
||||||
|
later via additional order positions still use the original order date. Attendees in
|
||||||
|
placed orders include those from all order states (pending, paid, cancelled, and
|
||||||
|
expired); attendees in paid orders include only those from paid orders and exclude
|
||||||
|
those from cancelled orders.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{% trans "Attendees by time" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div id="abt_chart" class="chart"></div>
|
||||||
|
<p class="help-block">
|
||||||
|
<small>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Attendees in orders paid in multiple instalments are shown using the date of the
|
||||||
|
final payment. Order dates reflect when the order was first placed; attendees added
|
||||||
|
later via additional order positions still use the original order date. Attendees in
|
||||||
|
placed orders include those from all order states (pending, paid, cancelled, and
|
||||||
|
expired); attendees in paid orders include only those from paid orders and exclude
|
||||||
|
those from cancelled orders.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% trans "Revenue over time" %}</h3>
|
<h3 class="panel-title">{% trans "Revenue over time" %}</h3>
|
||||||
@@ -177,6 +217,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script type="application/json" id="obd-data">{{ obd_data|escapejson }}</script>
|
<script type="application/json" id="obd-data">{{ obd_data|escapejson }}</script>
|
||||||
|
<script type="application/json" id="abd-data">{{ abd_data|escapejson }}</script>
|
||||||
|
<script type="application/json" id="abt-data">{{ abt_data|escapejson }}</script>
|
||||||
<script type="application/json" id="rev-data">{{ rev_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>
|
<script type="application/json" id="obp-data">{{ obp_data|escapejson }}</script>
|
||||||
<script type="application/text" id="currency">{{ request.event.currency }}</script>
|
<script type="application/text" id="currency">{{ request.event.currency }}</script>
|
||||||
|
|||||||
@@ -128,6 +128,52 @@ class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView)
|
|||||||
ctx['obd_data'] = json.dumps(data)
|
ctx['obd_data'] = json.dumps(data)
|
||||||
cache.set('statistics_obd_data' + ckey, ctx['obd_data'])
|
cache.set('statistics_obd_data' + ckey, ctx['obd_data'])
|
||||||
|
|
||||||
|
# Attendees by day/time
|
||||||
|
ctx['abd_data'] = cache.get('statistics_abd_data' + ckey)
|
||||||
|
ctx['abt_data'] = cache.get('statistics_abt_data' + ckey)
|
||||||
|
if not ctx['abd_data'] or not ctx['abt_data']:
|
||||||
|
opqs = OrderPosition.all.filter(order__event=self.request.event, item__admission=True).annotate(
|
||||||
|
payment_date=Subquery(op_date, output_field=DateTimeField())
|
||||||
|
)
|
||||||
|
if subevent:
|
||||||
|
opqs = opqs.filter(subevent=subevent)
|
||||||
|
|
||||||
|
ordered_by_day = {}
|
||||||
|
for p in opqs.values('order__datetime'):
|
||||||
|
day = p['order__datetime'].astimezone(tz).date()
|
||||||
|
ordered_by_day[day] = ordered_by_day.get(day, 0) + 1
|
||||||
|
|
||||||
|
paid_by_day = {}
|
||||||
|
for p in opqs.filter(payment_date__isnull=False, canceled=False, order__status=Order.STATUS_PAID).values('payment_date'):
|
||||||
|
day = p['payment_date'].astimezone(tz).date()
|
||||||
|
paid_by_day[day] = paid_by_day.get(day, 0) + 1
|
||||||
|
|
||||||
|
day_data = []
|
||||||
|
time_data = []
|
||||||
|
for d in dateutil.rrule.rrule(
|
||||||
|
dateutil.rrule.DAILY,
|
||||||
|
dtstart=min(ordered_by_day.keys()) if ordered_by_day else datetime.date.today(),
|
||||||
|
until=max(
|
||||||
|
max(ordered_by_day.keys() if paid_by_day else [datetime.date.today()]),
|
||||||
|
max(paid_by_day.keys() if paid_by_day else [datetime.date(1970, 1, 1)])
|
||||||
|
)):
|
||||||
|
d = d.date()
|
||||||
|
day_data.append({
|
||||||
|
'date': d.strftime('%Y-%m-%d'),
|
||||||
|
'ordered': ordered_by_day.get(d, 0),
|
||||||
|
'paid': paid_by_day.get(d, 0)
|
||||||
|
})
|
||||||
|
time_data.append({
|
||||||
|
'date': d.strftime('%Y-%m-%d'),
|
||||||
|
'ordered': (time_data[-1]["ordered"] if time_data else 0) + ordered_by_day.get(d, 0),
|
||||||
|
'paid': (time_data[-1]["paid"] if time_data else 0) + paid_by_day.get(d, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx['abd_data'] = json.dumps(day_data)
|
||||||
|
ctx['abt_data'] = json.dumps(time_data)
|
||||||
|
cache.set('statistics_abd_data' + ckey, ctx['abd_data'])
|
||||||
|
cache.set('statistics_abt_data' + ckey, ctx['abt_data'])
|
||||||
|
|
||||||
# Orders by product
|
# Orders by product
|
||||||
ctx['obp_data'] = cache.get('statistics_obp_data' + ckey)
|
ctx['obp_data'] = cache.get('statistics_obp_data' + ckey)
|
||||||
if not ctx['obp_data']:
|
if not ctx['obp_data']:
|
||||||
|
|||||||
Reference in New Issue
Block a user