Changeset - a445eed23988
[Not reviewed]
Merge
0 3 0
Christopher Neugebauer - 8 years ago 2016-09-20 09:41:47
chrisjrn@gmail.com
Merge branch 'chrisjrn/reports_20160919'
3 files changed with 56 insertions and 1 deletions:
0 comments (0 inline, 0 general)
registrasion/reporting/forms.py
Show inline comments
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 

	
 
from symposion.proposals import models as proposals_models
 

	
 
from django import forms
 

	
 
# Reporting forms.
 

	
 

	
 
class DiscountForm(forms.Form):
 
    discount = forms.ModelMultipleChoiceField(
 
        queryset=conditions.DiscountBase.objects.all(),
 
        required=False,
 
    )
 

	
 

	
 
class ProductAndCategoryForm(forms.Form):
 
    product = forms.ModelMultipleChoiceField(
 
        queryset=inventory.Product.objects.all(),
 
        required=False,
 
    )
 
    category = forms.ModelMultipleChoiceField(
 
        queryset=inventory.Category.objects.all(),
 
        required=False,
 
    )
 

	
 

	
 
class UserIdForm(forms.Form):
 
    user = forms.IntegerField(
 
        label="User ID",
 
        required=False,
 
    )
 

	
 

	
 
class ProposalKindForm(forms.Form):
 
    kind = forms.ModelMultipleChoiceField(
 
        queryset=proposals_models.ProposalKind.objects.all(),
 
        required=False,
 
    )
 

	
 

	
 

	
 
def model_fields_form_factory(model):
 
    ''' Creates a form for specifying fields from a model to display. '''
 

	
 
    fields = model._meta.get_fields()
 

	
 
    choices = []
 
    for field in fields:
 
        if hasattr(field, "verbose_name"):
 
            choices.append((field.name, field.verbose_name))
 

	
 
    class ModelFieldsForm(forms.Form):
 
        fields = forms.MultipleChoiceField(
 
            choices=choices,
 
            required=False,
 
        )
 

	
 
    return ModelFieldsForm
registrasion/reporting/views.py
Show inline comments
 
import forms
 

	
 
import collections
 
import datetime
 

	
 
from django.conf import settings
 
from django.contrib.auth.decorators import user_passes_test
 
from django.contrib.auth.models import User
 
from django.core.urlresolvers import reverse
 
from django.db import models
 
from django.db.models import F, Q
 
from django.db.models import Count, Max, 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 util
 
from registrasion import views
 

	
 
from symposion.schedule import models as schedule_models
 

	
 
from reports import get_all_reports
 
from reports import Links
 
from reports import ListReport
 
from reports import QuerysetReport
 
from reports import report_view
 

	
 

	
 
def CURRENCY():
 
    return models.DecimalField(decimal_places=2)
 

	
 

	
 
AttendeeProfile = util.get_object_from_name(settings.ATTENDEE_PROFILE_MODEL)
 

	
 

	
 
@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("Reconcilitation")
 
def reconciliation(request, form):
 
    ''' Shows the summary of sales, and the full history of payments and
 
    refunds into the system. '''
 

	
 
    return [
 
        sales_payment_summary(),
 
        items_sold(),
 
        payments(),
 
        credit_note_refunds(),
 
    ]
 

	
 

	
 
def items_sold():
 
    ''' Summarises the items sold and discounts granted for a given set of
 
    products, or products from categories. '''
 

	
 
    data = None
 
    headings = None
 

	
 
    line_items = commerce.LineItem.objects.filter(
 
        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 ListReport("Items sold", headings, data)
 

	
 

	
 
def sales_payment_summary():
 
    ''' Summarises paid items and payments. '''
 

	
 
    def value_or_zero(aggregate, key):
...
 
@@ -488,114 +491,151 @@ def attendee_list(request):
 
        ])
 

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

	
 
    return AttendeeListReport("Attendees", headings, data, link_view=attendee)
 

	
 

	
 
ProfileForm = forms.model_fields_form_factory(AttendeeProfile)
 
class ProductCategoryProfileForm(forms.ProductAndCategoryForm, ProfileForm):
 
    pass
 

	
 

	
 
@report_view(
 
    "Attendees By Product/Category",
 
    form_type=ProductCategoryProfileForm,
 
)
 
def attendee_data(request, form, user_id=None):
 
    ''' Lists attendees for a given product/category selection along with
 
    profile data.'''
 

	
 
    status_display = {
 
        commerce.Cart.STATUS_ACTIVE: "Unpaid",
 
        commerce.Cart.STATUS_PAID: "Paid",
 
        commerce.Cart.STATUS_RELEASED: "Refunded",
 
    }
 

	
 
    output = []
 

	
 
    products = form.cleaned_data["product"]
 
    categories = form.cleaned_data["category"]
 
    fields = form.cleaned_data["fields"]
 
    name_field = AttendeeProfile.name_field()
 

	
 
    items = commerce.ProductItem.objects.filter(
 
        Q(product__in=products) | Q(product__category__in=categories),
 
    ).exclude(
 
        cart__status=commerce.Cart.STATUS_RELEASED
 
    ).select_related(
 
        "cart", "product"
 
    ).order_by("cart__status")
 

	
 
    # Get all of the relevant attendee profiles in one hit.
 
    profiles = AttendeeProfile.objects.filter(
 
        attendee__user__cart__productitem__in=items
 
    ).select_related("attendee__user")
 
    by_user = {}
 
    for profile in profiles:
 
        by_user[profile.attendee.user] = profile
 

	
 
    # Group the responses per-field.
 
    for field in fields:
 
        field_verbose = AttendeeProfile._meta.get_field(field).verbose_name
 

	
 
        cart = "attendee__user__cart"
 
        cart_status = cart + "__status"
 
        product = cart + "__productitem__product"
 
        product_name = product + "__name"
 
        category_name = product + "__category__name"
 

	
 
        status_count = lambda status: Case(When(
 
                attendee__user__cart__status=status,
 
                then=Value(1),
 
            ),
 
            default=Value(0),
 
            output_field=models.fields.IntegerField(),
 
        )
 
        paid_count = status_count(commerce.Cart.STATUS_PAID)
 
        unpaid_count = status_count(commerce.Cart.STATUS_ACTIVE)
 

	
 
        p = profiles.order_by(product, field).values(
 
            product, product_name, category_name, field
 
        ).annotate(
 
            paid_count=Sum(paid_count),
 
            unpaid_count=Sum(unpaid_count),
 
        )
 
        output.append(ListReport(
 
            "Grouped by %s" % field_verbose,
 
            ["Product", field_verbose, "paid", "unpaid"],
 
            [
 
                (
 
                    "%s - %s" % (i[category_name], i[product_name]),
 
                    i[field],
 
                    i["paid_count"] or 0,
 
                    i["unpaid_count"] or 0,
 
                )
 
                for i in p
 
            ],
 
        ))
 

	
 
    # DO the report for individual attendees
 

	
 
    field_names = [
 
        AttendeeProfile._meta.get_field(field).verbose_name for field in fields
 
    ]
 

	
 
    headings = ["User ID", "Name", "Product", "Item Status"] + field_names
 
    headings = ["User ID", "Name", "Email", "Product", "Item Status"] + field_names
 
    data = []
 
    for item in items:
 
        profile = by_user[item.cart.user]
 
        line = [
 
            item.cart.user.id,
 
            getattr(profile, name_field),
 
            profile.attendee.user.email,
 
            item.product,
 
            status_display[item.cart.status],
 
        ] + [
 
            getattr(profile, field) for field in fields
 
        ]
 
        data.append(line)
 

	
 
    output.append(AttendeeListReport(
 
        "Attendees by item with profile data", headings, data, link_view=attendee
 
    ))
 
    return output
 

	
 

	
 
@report_view(
 
    "Speaker Registration Status",
 
    form_type=forms.ProposalKindForm,
 
)
 
def speaker_registrations(request, form):
 
    ''' Shows registration status for speakers with a given proposal kind. '''
 

	
 
    kinds = form.cleaned_data["kind"]
 

	
 
    presentations = schedule_models.Presentation.objects.filter(
 
        proposal_base__kind=kinds,
 
    ).exclude(
 
        cancelled=True,
 
    )
 

	
 
    users = User.objects.filter(
 
        Q(speaker_profile__presentations__in=presentations) |
 
        Q(speaker_profile__copresentations__in=presentations)
 
    )
 

	
 
    paid_carts = commerce.Cart.objects.filter(status=commerce.Cart.STATUS_PAID)
 

	
 
    paid_carts = Case(When(cart__in=paid_carts, then=Value(1)), default=Value(0), output_field=models.IntegerField())
 
    users = users.annotate(paid_carts=Sum(paid_carts))
 
    users=users.order_by("paid_carts")
 

	
 
    return QuerysetReport(
 
        "Speaker Registration Status",
 
        ["id", "speaker_profile__name", "email", "paid_carts",],
 
        users,
 
        link_view=attendee,
 
    )
 

	
 
    return []
registrasion/urls.py
Show inline comments
 
from reporting import views as rv
 

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

	
 
from .views import (
 
    amend_registration,
 
    checkout,
 
    credit_note,
 
    edit_profile,
 
    guided_registration,
 
    invoice,
 
    invoice_access,
 
    manual_payment,
 
    product_category,
 
    refund,
 
    review,
 
)
 

	
 

	
 
public = [
 
    url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"),
 
    url(r"^category/([0-9]+)$", product_category, name="product_category"),
 
    url(r"^checkout$", checkout, name="checkout"),
 
    url(r"^checkout/([0-9]+)$", 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"^review$", review, name="review"),
 
    url(r"^register/([0-9]+)$", guided_registration,
 
        name="guided_registration"),
 
]
 

	
 

	
 
reports = [
 
    url(r"^$", rv.reports_list, name="reports_list"),
 
    url(r"^attendee/?$", rv.attendee, name="attendee"),
 
    url(r"^attendee_data/?$", rv.attendee_data, name="attendee_data"),
 
    url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
 
    url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
 
    url(r"^discount_status/?$", rv.discount_status, name="discount_status"),
 
    url(
 
        r"^paid_invoices_by_date/?$",
 
        rv.paid_invoices_by_date,
 
        name="paid_invoices_by_date"
 
    ),
 
    url(r"^product_status/?$", rv.product_status, name="product_status"),
 
    url(r"^reconciliation/?$", rv.reconciliation, name="reconciliation"),
 
    url(
 
        r"^speaker_registrations/?$",
 
        rv.speaker_registrations,
 
        name="speaker_registrations",
 
    ),
 
]
 

	
 

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