Order overview: Allow to filter by date

This commit is contained in:
Raphael Michel
2019-07-07 19:45:22 +02:00
parent 42af8b1602
commit 197ec84f05
5 changed files with 186 additions and 34 deletions

View File

@@ -1,12 +1,16 @@
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from typing import Any, Dict, Iterable, List, Tuple
from django.db.models import Case, Count, F, Sum, Value, When
from django.db.models import (
Case, Count, DateTimeField, F, Max, OuterRef, Subquery, Sum, Value, When,
)
from django.utils.timezone import make_aware
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee
from pretix.base.models.orders import OrderFee, OrderPayment
from pretix.base.signals import order_fee_type_name
@@ -71,8 +75,9 @@ def dictsum(*dicts) -> dict:
return res
def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
Dict[str, Tuple[Decimal, Decimal]]]:
def order_overview(
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
items = event.items.all().select_related(
'category', # for re-grouping
).prefetch_related(
@@ -82,6 +87,38 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
qs = OrderPosition.all
if subevent:
qs = qs.filter(subevent=subevent)
if date_from and isinstance(date_from, date):
date_from = make_aware(datetime.combine(
date_from,
time(hour=0, minute=0, second=0, microsecond=0)
), event.timezone)
if date_until and isinstance(date_until, date):
date_until = make_aware(datetime.combine(
date_until + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), event.timezone)
if date_filter == 'order_date':
if date_from:
qs = qs.filter(order__datetime__gte=date_from)
if date_until:
qs = qs.filter(order__datetime__lt=date_until)
elif date_filter == 'last_payment_date':
p_date = OrderPayment.objects.filter(
order=OuterRef('order'),
state__in=[OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED],
payment_date__isnull=False
).values('order').annotate(
m=Max('payment_date')
).values('m').order_by()
qs = qs.annotate(payment_date=Subquery(p_date, output_field=DateTimeField()))
if date_from:
qs = qs.filter(payment_date__gte=date_from)
if date_until:
qs = qs.filter(payment_date__lt=date_until)
counters = qs.filter(
order__event=event
).annotate(
@@ -153,14 +190,26 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
payment_items = []
if not subevent:
counters = OrderFee.all.filter(
qs = OrderFee.all.filter(
order__event=event
).annotate(
status=Case(
When(canceled=True, then=Value('c')),
default=F('order__status')
)
).values(
)
if date_filter == 'order_date':
if date_from:
qs = qs.filter(order__datetime__gte=date_from)
if date_until:
qs = qs.filter(order__datetime__lt=date_until)
elif date_filter == 'last_payment_date':
qs = qs.annotate(payment_date=Subquery(p_date, output_field=DateTimeField()))
if date_from:
qs = qs.filter(payment_date__gte=date_from)
if date_until:
qs = qs.filter(payment_date__lt=date_until)
counters = qs.values(
'fee_type', 'internal_type', 'status'
).annotate(cnt=Count('id'), value=Sum('value'), tax_value=Sum('tax_value')).order_by()

View File

@@ -940,3 +940,51 @@ class RefundFilterForm(FilterForm):
OrderRefund.REFUND_STATE_EXTERNAL])
return qs
class OverviewFilterForm(FilterForm):
subevent = forms.ModelChoiceField(
label=pgettext_lazy('subevent', 'Date'),
queryset=SubEvent.objects.none(),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
date_axis = forms.ChoiceField(
label=_('Date filter'),
choices=(
('', _('Filter by…')),
('order_date', _('Order date')),
('last_payment_date', _('Date of last successful payment')),
),
required=False,
)
date_from = forms.DateField(
label=_('Date from'),
required=False,
widget=DatePickerWidget,
)
date_until = forms.DateField(
label=_('Date until'),
required=False,
widget=DatePickerWidget,
)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
if self.event.has_subevents:
self.fields['subevent'].queryset = self.event.subevents.all()
self.fields['subevent'].widget = Select2(
attrs={
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
elif 'subevent':
del self.fields['subevent']

View File

@@ -1,5 +1,6 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load order_overview %}
{% block title %}{% trans "Order overview" %}{% endblock %}
{% block content %}
@@ -12,11 +13,36 @@
</div>
</div>
<h1>{% trans "Order overview" %}</h1>
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
<div class="row filter-form">
<form class="" action="" method="get">
{% if request.event.has_subevents %}
<div class="col-lg-2 col-sm-3 col-xs-6">
{% bootstrap_field filter_form.subevent layout='inline' %}
</div>
<div class="col-lg-2 col-sm-3 col-xs-6">
{% bootstrap_field filter_form.date_axis layout='inline' %}
</div>
{% else %}
<div class="col-lg-4 col-sm-6 col-xs-6">
{% bootstrap_field filter_form.date_axis layout='inline' %}
</div>
{% endif %}
<div class="col-lg-2 col-sm-6 col-xs-6">
{% bootstrap_field filter_form.date_from layout='inline' %}
</div>
<div class="col-lg-2 col-sm-6 col-xs-6">
{% bootstrap_field filter_form.date_until layout='inline' %}
</div>
<div class="col-lg-1 col-lg-offset-3 col-sm-6 col-xs-6">
<button class="btn btn-primary btn-block" type="submit">
<span class="fa fa-filter"></span>
<span class="hidden-md">
{% trans "Filter" %}
</span>
</button>
</div>
</form>
{% endif %}
</div>
{% if subevent_warning %}
<div class="alert alert-info">
{% blocktrans trimmed context "subevent" %}

View File

@@ -38,7 +38,6 @@ from pretix.base.models import (
Item, ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
generate_position_secret, generate_secret,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import (
OrderFee, OrderPayment, OrderPosition, OrderRefund,
)
@@ -66,7 +65,9 @@ from pretix.base.templatetags.money import money_filter
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.views.mixins import OrderQuestionsViewMixin
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.filter import EventOrderFilterForm, RefundFilterForm
from pretix.control.forms.filter import (
EventOrderFilterForm, OverviewFilterForm, RefundFilterForm,
)
from pretix.control.forms.orders import (
CancelForm, CommentForm, ConfirmPaymentForm, ExporterForm, ExtendForm,
MarkPaidForm, OrderContactForm, OrderLocaleForm, OrderMailForm,
@@ -1593,21 +1594,32 @@ class OverView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/orders/overview.html'
permission = 'can_view_orders'
@cached_property
def filter_form(self):
return OverviewFilterForm(data=self.request.GET, event=self.request.event)
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
subevent = None
if self.request.GET.get("subevent", "") != "" and self.request.event.has_subevents:
i = self.request.GET.get("subevent", "")
try:
subevent = self.request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
ctx['items_by_category'], ctx['total'] = order_overview(self.request.event, subevent=subevent)
ctx['subevent_warning'] = self.request.event.has_subevents and subevent and (
if self.filter_form.is_valid():
ctx['items_by_category'], ctx['total'] = order_overview(
self.request.event,
subevent=self.filter_form.cleaned_data.get('subevent'),
date_filter=self.filter_form.cleaned_data['date_axis'],
date_from=self.filter_form.cleaned_data['date_from'],
date_until=self.filter_form.cleaned_data['date_until'],
)
else:
ctx['items_by_category'], ctx['total'] = order_overview(
self.request.event,
)
ctx['subevent_warning'] = (
self.request.event.has_subevents and
self.filter_form.is_valid() and
self.filter_form.cleaned_data.get('subevent') and
OrderFee.objects.filter(order__event=self.request.event).exclude(value=0).exists()
)
ctx['filter_form'] = self.filter_form
return ctx

View File

@@ -3,6 +3,7 @@ from collections import OrderedDict, defaultdict
from decimal import Decimal
import pytz
from dateutil.parser import parse
from django import forms
from django.conf import settings
from django.contrib.staticfiles import finders
@@ -11,7 +12,7 @@ from django.db.models import Max, OuterRef, Subquery, Sum
from django.template.defaultfilters import floatformat
from django.utils.formats import date_format, localize
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import pgettext, pgettext_lazy, ugettext as _
from django.utils.translation import pgettext, ugettext as _
from reportlab.lib import colors
from pretix.base.decimal import round_decimal
@@ -20,6 +21,7 @@ from pretix.base.models import Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee, OrderPayment
from pretix.base.services.stats import order_overview
from pretix.control.forms.filter import OverviewFilterForm
class ReportlabExportMixin:
@@ -160,6 +162,11 @@ class OverviewReport(Report):
from reportlab.platypus import Paragraph, Spacer, TableStyle, Table
from reportlab.lib.units import mm
if form_data.get('date_from'):
form_data['date_from'] = parse(form_data['date_from'])
if form_data.get('date_until'):
form_data['date_until'] = parse(form_data['date_until'])
headlinestyle = self.get_style()
headlinestyle.fontSize = 15
headlinestyle.fontName = 'OpenSansBd'
@@ -190,7 +197,17 @@ class OverviewReport(Report):
Paragraph(_('Orders by product'), headlinestyle),
Spacer(1, 5 * mm)
]
if self.form_data.get('subevent'):
if form_data.get('date_axis'):
story += [
Paragraph(_('{axis} between {start} and {end}').format(
axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')],
start=date_format(form_data.get('date_from'), 'SHORT_DATE_FORMAT') if form_data.get('date_from') else '',
end=date_format(form_data.get('date_until'), 'SHORT_DATE_FORMAT') if form_data.get('date_until') else '',
), self.get_style()),
Spacer(1, 5 * mm)
]
if form_data.get('subevent'):
try:
subevent = self.event.subevents.get(pk=self.form_data.get('subevent'))
except SubEvent.DoesNotExist:
@@ -215,7 +232,13 @@ class OverviewReport(Report):
],
]
items_by_category, total = order_overview(self.event, subevent=self.form_data.get('subevent'))
items_by_category, total = order_overview(
self.event,
subevent=form_data.get('subevent'),
date_filter=form_data.get('date_axis'),
date_from=form_data.get('date_from'),
date_until=form_data.get('date_until'),
)
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
states = (
('canceled', Order.STATUS_CANCELED),
@@ -264,15 +287,9 @@ class OverviewReport(Report):
@property
def export_form_fields(self) -> dict:
d = OrderedDict()
if self.event.has_subevents:
d['subevent'] = forms.ModelChoiceField(
self.event.subevents.all(),
label=pgettext_lazy('subevent', 'Date'),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
return d
f = OverviewFilterForm(event=self.event)
del f.fields['ordering']
return f.fields
class OrderTaxListReport(Report):