diff --git a/src/pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js b/src/pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js index 33c2b3319..0fdd45705 100644 --- a/src/pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js +++ b/src/pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js @@ -19,6 +19,30 @@ $(function () { fillOpacity: 0.3, 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({ element: 'rev_chart', data: JSON.parse($("#rev-data").html()), diff --git a/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html b/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html index d6d3601d4..db7f08b89 100644 --- a/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html +++ b/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html @@ -31,6 +31,46 @@

+
+
+

{% trans "Attendees by day" %}

+
+
+
+

+ + {% 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 %} + +

+
+
+
+
+

{% trans "Attendees by time" %}

+
+
+
+

+ + {% 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 %} + +

+
+

{% trans "Revenue over time" %}

@@ -177,6 +217,8 @@
{% endif %} + + diff --git a/src/pretix/plugins/statistics/views.py b/src/pretix/plugins/statistics/views.py index 7eacdd4f9..17e9b4833 100644 --- a/src/pretix/plugins/statistics/views.py +++ b/src/pretix/plugins/statistics/views.py @@ -128,6 +128,52 @@ class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView) ctx['obd_data'] = json.dumps(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 ctx['obp_data'] = cache.get('statistics_obp_data' + ckey) if not ctx['obp_data']: