Changeset - c64d0eaab857
[Not reviewed]
Merge
0 3 0
Christopher Neugebauer - 8 years ago 2016-09-03 03:53:09
chrisjrn@gmail.com
Merge branch 'chrisjrn/more_reports'
3 files changed with 76 insertions and 21 deletions:
0 comments (0 inline, 0 general)
registrasion/models/people.py
Show inline comments
 
from registrasion import util
 

	
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from model_utils.managers import InheritanceManager
 

	
 

	
 
# User models
 

	
 
@python_2_unicode_compatible
 
class Attendee(models.Model):
 
    ''' Miscellaneous user-related data. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        return "%s" % self.user
 

	
 
    @staticmethod
 
    def get_instance(user):
 
        ''' Returns the instance of attendee for the given user, or creates
 
        a new one. '''
 
        try:
 
            return Attendee.objects.get(user=user)
 
        except ObjectDoesNotExist:
 
            return Attendee.objects.create(user=user)
 

	
 
    def save(self, *a, **k):
 
        while not self.access_code:
 
            access_code = util.generate_access_code()
 
            if Attendee.objects.filter(access_code=access_code).count() == 0:
 
                self.access_code = access_code
 
        return super(Attendee, self).save(*a, **k)
 

	
 
    user = models.OneToOneField(User, on_delete=models.CASCADE)
 
    # Badge/profile is linked
 
    access_code = models.CharField(
 
        max_length=6,
 
        unique=True,
 
        db_index=True,
 
    )
 
    completed_registration = models.BooleanField(default=False)
 
    guided_categories_complete = models.ManyToManyField("category")
 

	
 

	
 
class AttendeeProfileBase(models.Model):
 
    ''' Information for an attendee's badge and related preferences.
 
    Subclass this in your Django site to ask for attendee information in your
 
    registration progess.
 
     '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    objects = InheritanceManager()
 

	
 
    @classmethod
 
    def name_field(cls):
 
        '''
 
        Returns:
 
            The name of a field that stores the attendee's name. This is used
 
            to pre-fill the attendee's name from their Speaker profile, if they
 
            have one.
 
        '''
 
        return None
 

	
 
    def attendee_name(self):
 
        if type(self) == AttendeeProfileBase:
 
            real = AttendeeProfileBase.objects.get_subclass(id=self.id)
 
        else:
 
            real = self
 
        return getattr(real, real.name_field())
 

	
 
    def invoice_recipient(self):
 
        '''
 

	
 
        Returns:
 
            A representation of this attendee profile for the purpose
 
            of rendering to an invoice. This should include any information
 
            that you'd usually include on an invoice. Override in subclasses.
 
        '''
 

	
 
        # Manual dispatch to subclass. Fleh.
 
        slf = AttendeeProfileBase.objects.get_subclass(id=self.id)
 
        # Actually compare the functions.
 
        if type(slf).invoice_recipient != type(self).invoice_recipient:
 
            return type(slf).invoice_recipient(slf)
 

	
 
        # Return a default
 
        return slf.attendee.user.username
 

	
 
    attendee = models.OneToOneField(Attendee, on_delete=models.CASCADE)
registrasion/reporting/views.py
Show inline comments
 
import forms
 

	
 
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 Count, Sum
 
from django.db.models import Case, When, Value
 
from django.shortcuts import render
 

	
 
from registrasion.controllers.item import ItemController
 
from registrasion.models import commerce
 
from registrasion.models import people
 
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("Reconcilitation")
 
def reconciliation(request, form):
 
    ''' Reconciles all sales in the system with the payments in the
 
    system. '''
 

	
 
    headings = ["Thing", "Total"]
 
    data = []
 

	
 
    sales = commerce.LineItem.objects.filter(
 
        invoice__status=commerce.Invoice.STATUS_PAID,
 
    ).values(
 
        "price", "quantity"
 
    ).aggregate(total=Sum(F("price") * F("quantity")))
 

	
 
    data.append(["Paid items", sales["total"]])
 

	
 
    payments = commerce.PaymentBase.objects.values(
 
        "amount",
 
    ).aggregate(total=Sum("amount"))
 

	
 
    data.append(["Payments", payments["total"]])
 

	
 
    ucn = commerce.CreditNote.unclaimed().values(
 
        "amount"
 
    ).aggregate(total=Sum("amount"))
 

	
 
    data.append(["Unclaimed credit notes", 0 - ucn["total"]])
 

	
 
    data.append([
 
        "(Money not on invoices)",
 
        sales["total"] - payments["total"] - ucn["total"],
 
    ])
 

	
 
    return Report("Sales and Payments", 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")
 

	
 

	
 
@report_view("Attendee", form_type=forms.UserIdForm)
 
def attendee(request, form, attendee_id=None):
 
    ''' Returns a list of all manifested attendees if no attendee is specified,
 
    else displays the attendee manifest. '''
 

	
 
    if attendee_id is None and not form.has_changed():
 
        return attendee_list(request)
 

	
 
    if form.cleaned_data["user"] is not None:
 
        attendee_id = form.cleaned_data["user"]
 

	
 
    attendee = people.Attendee.objects.get(id=attendee_id)
 

	
 
    reports = []
 

	
 
    # TODO: METADATA.
 

	
 
    ic = ItemController(attendee.user)
 
    # Paid products
 
    headings = ["Product", "Quantity"]
 
    data = []
 

	
 
    for pq in ic.items_purchased():
 
        data.append([
 
            pq.product,
 
            pq.quantity,
 
        ])
 

	
 
    reports.append(Report("Paid Products", headings, data))
 

	
 
    # Unpaid products
 
    headings = ["Product", "Quantity"]
 
    data = []
 

	
 
    for pq in ic.items_pending():
 
        data.append([
 
            pq.product,
 
            pq.quantity,
 
        ])
 

	
 
    reports.append( Report("Unpaid Products", headings, data))
 

	
 
    # Invoices
 
    headings = ["Invoice ID", "Status", "Value"]
 
    data = []
 

	
 
    invoices = commerce.Invoice.objects.filter(
 
        user=attendee.user,
 
    )
 
    for invoice in invoices:
 
        data.append([
 
            invoice.id, invoice.get_status_display(), invoice.value,
 
        ])
 

	
 
    reports.append(Report("Invoices", headings, data, link_view="invoice"))
 

	
 
    # Credit Notes
 
    headings = ["Note ID", "Status", "Value"]
 
    data = []
 

	
 
    credit_notes = commerce.CreditNote.objects.filter(
 
        invoice__user=attendee.user,
 
    )
 
    for credit_note in credit_notes:
 
        data.append([
 
            credit_note.id, credit_note.status, credit_note.value,
 
        ])
 

	
 
    reports.append(
 
        Report("Credit Notes", headings, data, link_view="credit_note")
 
    )
 

	
 
    # All payments
 
    headings = ["To Invoice", "Payment ID", "Reference", "Amount"]
 
    data = []
 

	
 
    payments = commerce.PaymentBase.objects.filter(
 
        invoice__user=attendee.user,
 
    )
 
    for payment in payments:
 
        data.append([
 
            payment.invoice.id, payment.id, payment.reference, payment.amount,
 
        ])
 

	
 
    reports.append(
 
        Report("Payments", headings, data, link_view="invoice")
 
    )
 

	
 

	
 
    return reports
 

	
 

	
 
def attendee_list(request):
 
    ''' Returns a list of all attendees. '''
 

	
 
    attendees = people.Attendee.objects.all().select_related(
 
        "attendeeprofilebase",
 
        "user",
 
    )
 

	
 
    attendees = attendees.values("id", "user__email").annotate(
 
    attendees = attendees.annotate(
 
        has_registered=Count(
 
            Q(user__invoice__status=commerce.Invoice.STATUS_PAID)
 
        ),
 
    )
 

	
 
    headings = [
 
        "User ID", "Email", "Has registered",
 
        "User ID", "Name", "Email", "Has registered",
 
    ]
 

	
 
    data = []
 

	
 
    for attendee in attendees:
 
        data.append([
 
            attendee["id"],
 
            attendee["user__email"],
 
            attendee["has_registered"],
 
            attendee.id,
 
            attendee.attendeeprofilebase.attendee_name(),
 
            attendee.user.email,
 
            attendee.has_registered > 0,
 
        ])
 

	
 
    # Sort by whether they've registered, then ID.
 
    data.sort(key=lambda attendee: (-attendee[2], attendee[0]))
 
    data.sort(key=lambda attendee: (-attendee[3], attendee[0]))
 

	
 
    return Report("Attendees", headings, data, link_view="attendee")
registrasion/urls.py
Show inline comments
 
from reporting import views as reporting_views
 
from reporting import views as rv
 

	
 
from django.conf.urls import include
 
from django.conf.urls import url
 

	
 
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"^attendee/?$", reporting_views.attendee, name="attendee"),
 
    url(r"^attendee/([0-9]*)$", reporting_views.attendee, name="attendee"),
 
    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"),
 
    url(r"^$", rv.reports_list, name="reports_list"),
 
    url(r"^attendee/?$", rv.attendee, name="attendee"),
 
    url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
 
    url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
 
    url(r"^items_sold/?$", rv.items_sold, name="items_sold"),
 
    url(r"^product_status/?$", rv.product_status, name="product_status"),
 
    url(r"^reconciliation/?$", rv.reconciliation, name="reconciliation"),
 
]
 

	
 

	
 
urlpatterns = [
 
    url(r"^reports/", include(reports)),
 
    url(r"^", include(public))  # This one must go last.
 
]
0 comments (0 inline, 0 general)