commit
4b6b221086
6 changed files with 351 additions and 20 deletions
|
@ -345,3 +345,16 @@ class VoucherForm(forms.Form):
|
|||
help_text="If you have a voucher code, enter it here",
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
# Staff-facing forms.
|
||||
|
||||
class ProductAndCategoryForm(forms.Form):
|
||||
product = forms.ModelMultipleChoiceField(
|
||||
queryset=inventory.Product.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
category = forms.ModelMultipleChoiceField(
|
||||
queryset=inventory.Category.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
|
|
|
@ -46,7 +46,7 @@ class Category(models.Model):
|
|||
from this Category that each attendee may claim. This extends
|
||||
across multiple Invoices.
|
||||
|
||||
display_order (int): An ascending order for displaying the Categories
|
||||
order (int): An ascending order for displaying the Categories
|
||||
available. By convention, your Category for ticket types should
|
||||
have the lowest display order.
|
||||
'''
|
||||
|
@ -129,7 +129,7 @@ class Product(models.Model):
|
|||
pay for it. This reservation duration determines how long an item
|
||||
should be allowed to be reserved whilst being unpaid.
|
||||
|
||||
display_order (int): An ascending order for displaying the Products
|
||||
order (int): An ascending order for displaying the Products
|
||||
within each Category.
|
||||
|
||||
'''
|
||||
|
|
0
registrasion/reporting/__init__.py
Normal file
0
registrasion/reporting/__init__.py
Normal file
90
registrasion/reporting/reports.py
Normal file
90
registrasion/reporting/reports.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.shortcuts import render
|
||||
from functools import wraps
|
||||
|
||||
from registrasion import views
|
||||
|
||||
|
||||
''' A list of report views objects that can be used to load a list of
|
||||
reports. '''
|
||||
_all_report_views = []
|
||||
|
||||
|
||||
class Report(object):
|
||||
|
||||
def __init__(self, title, headings, data, link_view=None):
|
||||
self._headings = headings
|
||||
self._data = data
|
||||
self._link_view = link_view
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
''' Returns the title for this report. '''
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def headings(self):
|
||||
''' Returns the headings for the table. '''
|
||||
return self._headings
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
''' Returns the data rows for the table. '''
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def link_view(self):
|
||||
''' Returns the URL name or the view callable that can be used to
|
||||
view the row's detail. The left-most value is passed into `reverse`
|
||||
as an argument. '''
|
||||
|
||||
return self._link_view
|
||||
|
||||
|
||||
def report_view(title, form_type=None):
|
||||
''' Decorator that converts a report view function into something that
|
||||
displays a Report.
|
||||
|
||||
Arguments:
|
||||
title (str):
|
||||
The title of the report.
|
||||
form_type (Optional[forms.Form]):
|
||||
A form class that can make this report display things. If not
|
||||
supplied, no form will be displayed.
|
||||
|
||||
'''
|
||||
|
||||
def _report(view):
|
||||
|
||||
@wraps(view)
|
||||
@user_passes_test(views._staff_only)
|
||||
def inner_view(request, *a, **k):
|
||||
|
||||
if form_type is not None:
|
||||
form = form_type(request.GET)
|
||||
form.is_valid()
|
||||
else:
|
||||
form = None
|
||||
|
||||
report = view(request, form, *a, **k)
|
||||
|
||||
ctx = {
|
||||
"title": title,
|
||||
"form": form,
|
||||
"report": report,
|
||||
}
|
||||
|
||||
return render(request, "registrasion/report.html", ctx)
|
||||
|
||||
# Add this report to the list of reports.
|
||||
_all_report_views.append(inner_view)
|
||||
|
||||
# Return the callable
|
||||
return inner_view
|
||||
return _report
|
||||
|
||||
|
||||
def get_all_reports():
|
||||
''' Returns all the views that have been registered with @report '''
|
||||
|
||||
return list(_all_report_views)
|
196
registrasion/reporting/views.py
Normal file
196
registrasion/reporting/views.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import Sum
|
||||
from django.db.models import Case, When, Value
|
||||
from django.shortcuts import render
|
||||
|
||||
from registrasion import forms
|
||||
from registrasion.models import commerce
|
||||
from registrasion import views
|
||||
|
||||
from reports import get_all_reports
|
||||
from reports import Report
|
||||
from reports import report_view
|
||||
|
||||
|
||||
@user_passes_test(views._staff_only)
|
||||
def reports_list(request):
|
||||
''' Lists all of the reports currently available. '''
|
||||
|
||||
reports = []
|
||||
|
||||
for report in get_all_reports():
|
||||
reports.append({
|
||||
"name": report.__name__,
|
||||
"url": reverse(report),
|
||||
"description": report.__doc__,
|
||||
})
|
||||
|
||||
reports.sort(key=lambda report: report["name"])
|
||||
|
||||
ctx = {
|
||||
"reports": reports,
|
||||
}
|
||||
|
||||
return render(request, "registrasion/reports_list.html", ctx)
|
||||
|
||||
|
||||
# Report functions
|
||||
|
||||
|
||||
@report_view("Paid items", form_type=forms.ProductAndCategoryForm)
|
||||
def items_sold(request, form):
|
||||
''' Summarises the items sold and discounts granted for a given set of
|
||||
products, or products from categories. '''
|
||||
|
||||
data = None
|
||||
headings = None
|
||||
|
||||
products = form.cleaned_data["product"]
|
||||
categories = form.cleaned_data["category"]
|
||||
|
||||
line_items = commerce.LineItem.objects.filter(
|
||||
Q(product__in=products) | Q(product__category__in=categories),
|
||||
invoice__status=commerce.Invoice.STATUS_PAID,
|
||||
).select_related("invoice")
|
||||
|
||||
line_items = line_items.order_by(
|
||||
# sqlite requires an order_by for .values() to work
|
||||
"-price", "description",
|
||||
).values(
|
||||
"price", "description",
|
||||
).annotate(
|
||||
total_quantity=Sum("quantity"),
|
||||
)
|
||||
|
||||
print line_items
|
||||
|
||||
headings = ["Description", "Quantity", "Price", "Total"]
|
||||
|
||||
data = []
|
||||
total_income = 0
|
||||
for line in line_items:
|
||||
cost = line["total_quantity"] * line["price"]
|
||||
data.append([
|
||||
line["description"], line["total_quantity"],
|
||||
line["price"], cost,
|
||||
])
|
||||
total_income += cost
|
||||
|
||||
data.append([
|
||||
"(TOTAL)", "--", "--", total_income,
|
||||
])
|
||||
|
||||
return Report("Paid items", headings, data)
|
||||
|
||||
|
||||
@report_view("Product status", form_type=forms.ProductAndCategoryForm)
|
||||
def product_status(request, form):
|
||||
''' Summarises the inventory status of the given items, grouping by
|
||||
invoice status. '''
|
||||
|
||||
products = form.cleaned_data["product"]
|
||||
categories = form.cleaned_data["category"]
|
||||
|
||||
items = commerce.ProductItem.objects.filter(
|
||||
Q(product__in=products) | Q(product__category__in=categories),
|
||||
).select_related("cart", "product")
|
||||
|
||||
items = items.annotate(
|
||||
is_reserved=Case(
|
||||
When(cart__in=commerce.Cart.reserved_carts(), then=Value(1)),
|
||||
default=Value(0),
|
||||
output_field=models.BooleanField(),
|
||||
),
|
||||
)
|
||||
|
||||
items = items.order_by(
|
||||
"product__category__order",
|
||||
"product__order",
|
||||
).values(
|
||||
"product",
|
||||
"product__category__name",
|
||||
"product__name",
|
||||
).annotate(
|
||||
total_paid=Sum(Case(
|
||||
When(
|
||||
cart__status=commerce.Cart.STATUS_PAID,
|
||||
then=F("quantity"),
|
||||
),
|
||||
default=Value(0),
|
||||
)),
|
||||
total_refunded=Sum(Case(
|
||||
When(
|
||||
cart__status=commerce.Cart.STATUS_RELEASED,
|
||||
then=F("quantity"),
|
||||
),
|
||||
default=Value(0),
|
||||
)),
|
||||
total_unreserved=Sum(Case(
|
||||
When(
|
||||
(
|
||||
Q(cart__status=commerce.Cart.STATUS_ACTIVE) &
|
||||
Q(is_reserved=False)
|
||||
),
|
||||
then=F("quantity"),
|
||||
),
|
||||
default=Value(0),
|
||||
)),
|
||||
total_reserved=Sum(Case(
|
||||
When(
|
||||
(
|
||||
Q(cart__status=commerce.Cart.STATUS_ACTIVE) &
|
||||
Q(is_reserved=True)
|
||||
),
|
||||
then=F("quantity"),
|
||||
),
|
||||
default=Value(0),
|
||||
)),
|
||||
)
|
||||
|
||||
headings = [
|
||||
"Product", "Paid", "Reserved", "Unreserved", "Refunded",
|
||||
]
|
||||
data = []
|
||||
|
||||
for item in items:
|
||||
data.append([
|
||||
"%s - %s" % (
|
||||
item["product__category__name"], item["product__name"]
|
||||
),
|
||||
item["total_paid"],
|
||||
item["total_reserved"],
|
||||
item["total_unreserved"],
|
||||
item["total_refunded"],
|
||||
])
|
||||
|
||||
return Report("Inventory", headings, data)
|
||||
|
||||
|
||||
@report_view("Credit notes")
|
||||
def credit_notes(request, form):
|
||||
''' Shows all of the credit notes in the system. '''
|
||||
|
||||
notes = commerce.CreditNote.objects.all().select_related(
|
||||
"creditnoterefund",
|
||||
"creditnoteapplication",
|
||||
"invoice",
|
||||
"invoice__user__attendee__attendeeprofilebase",
|
||||
)
|
||||
|
||||
headings = [
|
||||
"id", "Owner", "Status", "Value",
|
||||
]
|
||||
|
||||
data = []
|
||||
for note in notes:
|
||||
data.append([
|
||||
note.id,
|
||||
note.invoice.user.attendee.attendeeprofilebase.invoice_recipient(),
|
||||
note.status,
|
||||
note.value,
|
||||
])
|
||||
|
||||
return Report("Credit Notes", headings, data, link_view="credit_note")
|
|
@ -1,22 +1,54 @@
|
|||
import views
|
||||
from reporting import views as reporting_views
|
||||
|
||||
from django.conf.urls import url, patterns
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = patterns(
|
||||
"registrasion.views",
|
||||
url(r"^category/([0-9]+)$", "product_category", name="product_category"),
|
||||
url(r"^checkout$", "checkout", name="checkout"),
|
||||
url(r"^credit_note/([0-9]+)$", views.credit_note, name="credit_note"),
|
||||
url(r"^invoice/([0-9]+)$", "invoice", name="invoice"),
|
||||
url(r"^invoice/([0-9]+)/([A-Z0-9]+)$", views.invoice, name="invoice"),
|
||||
url(r"^invoice/([0-9]+)/manual_payment$",
|
||||
views.manual_payment, name="manual_payment"),
|
||||
url(r"^invoice/([0-9]+)/refund$",
|
||||
views.refund, name="refund"),
|
||||
url(r"^invoice_access/([A-Z0-9]+)$", views.invoice_access,
|
||||
name="invoice_access"),
|
||||
url(r"^profile$", "edit_profile", name="attendee_edit"),
|
||||
url(r"^register$", "guided_registration", name="guided_registration"),
|
||||
url(r"^register/([0-9]+)$", "guided_registration",
|
||||
name="guided_registration"),
|
||||
from .views import (
|
||||
product_category,
|
||||
checkout,
|
||||
credit_note,
|
||||
invoice,
|
||||
manual_payment,
|
||||
refund,
|
||||
invoice_access,
|
||||
edit_profile,
|
||||
guided_registration,
|
||||
)
|
||||
|
||||
|
||||
public = [
|
||||
url(r"^category/([0-9]+)$", product_category, name="product_category"),
|
||||
url(r"^checkout$", checkout, name="checkout"),
|
||||
url(r"^credit_note/([0-9]+)$", credit_note, name="credit_note"),
|
||||
url(r"^invoice/([0-9]+)$", invoice, name="invoice"),
|
||||
url(r"^invoice/([0-9]+)/([A-Z0-9]+)$", invoice, name="invoice"),
|
||||
url(r"^invoice/([0-9]+)/manual_payment$",
|
||||
manual_payment, name="manual_payment"),
|
||||
url(r"^invoice/([0-9]+)/refund$",
|
||||
refund, name="refund"),
|
||||
url(r"^invoice_access/([A-Z0-9]+)$", invoice_access,
|
||||
name="invoice_access"),
|
||||
url(r"^profile$", edit_profile, name="attendee_edit"),
|
||||
url(r"^register$", guided_registration, name="guided_registration"),
|
||||
url(r"^register/([0-9]+)$", guided_registration,
|
||||
name="guided_registration"),
|
||||
]
|
||||
|
||||
|
||||
reports = [
|
||||
url(r"^$", reporting_views.reports_list, name="reports_list"),
|
||||
url(r"^credit_notes/?$", reporting_views.credit_notes, name="credit_notes"),
|
||||
url(
|
||||
r"^product_status/?$",
|
||||
reporting_views.product_status,
|
||||
name="product_status",
|
||||
),
|
||||
url(r"^items_sold/?$", reporting_views.items_sold, name="items_sold"),
|
||||
]
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^reports/", include(reports)),
|
||||
url(r"^", include(public)) # This one must go last.
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue