Changeset - 4b6b221086d2
[Not reviewed]
Merge
0 3 3
Christopher Neugebauer - 8 years ago 2016-09-02 01:32:28
chrisjrn@gmail.com
Merge branch 'chrisjrn/reports'

Fixes #46
6 files changed with 348 insertions and 17 deletions:
0 comments (0 inline, 0 general)
registrasion/forms.py
Show inline comments
...
 
@@ -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,
 
    )
registrasion/models/inventory.py
Show inline comments
...
 
@@ -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.
 

	
 
    '''
registrasion/reporting/__init__.py
Show inline comments
 
new file 100644
registrasion/reporting/reports.py
Show inline comments
 
new file 100644
 
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)
registrasion/reporting/views.py
Show inline comments
 
new file 100644
 
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")
registrasion/urls.py
Show inline comments
 
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"),
 
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$",
 
        views.manual_payment, name="manual_payment"),
 
        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,
 
        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",
 
    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.
 
]
0 comments (0 inline, 0 general)