2016-08-25 10:33:19 +00:00
|
|
|
import forms
|
2016-09-01 23:31:12 +00:00
|
|
|
import views
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-09-01 23:31:12 +00:00
|
|
|
from django.contrib.auth.decorators import user_passes_test
|
2016-08-26 04:59:54 +00:00
|
|
|
from django.db import models
|
2016-08-26 05:53:33 +00:00
|
|
|
from django.db.models import F, Q
|
2016-08-25 11:05:02 +00:00
|
|
|
from django.db.models import Sum
|
2016-08-26 04:59:54 +00:00
|
|
|
from django.db.models import Case, When, Value
|
2016-09-01 23:31:12 +00:00
|
|
|
from django.http import Http404
|
2016-08-25 10:33:19 +00:00
|
|
|
from django.shortcuts import render
|
|
|
|
from functools import wraps
|
|
|
|
|
|
|
|
from models import commerce
|
2016-08-26 04:59:54 +00:00
|
|
|
from models import inventory
|
2016-08-25 10:33:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
All reports must be viewable by staff only (permissions?)
|
|
|
|
|
|
|
|
Reports can have:
|
|
|
|
|
|
|
|
A form
|
|
|
|
* Reports are all *gettable* - you can save a URL and get back to the same
|
|
|
|
report
|
|
|
|
* Fetching a report *cannot* break the underlying data.
|
|
|
|
A table
|
|
|
|
* Headings
|
|
|
|
* Data lines
|
|
|
|
* Formats are pluggable
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
class Report(object):
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
def __init__(self, title, headings, data):
|
2016-08-25 10:33:19 +00:00
|
|
|
self._headings = headings
|
|
|
|
self._data = data
|
|
|
|
|
2016-08-25 11:10:07 +00:00
|
|
|
@property
|
|
|
|
def title(self):
|
2016-08-26 05:11:08 +00:00
|
|
|
''' Returns the title for this report. '''
|
2016-08-25 11:10:07 +00:00
|
|
|
return self._title
|
|
|
|
|
2016-08-25 10:33:19 +00:00
|
|
|
@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
|
|
|
|
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
def report(title, form_type):
|
2016-08-25 10:33:19 +00:00
|
|
|
''' Decorator that converts a report view function into something that
|
|
|
|
displays a Report.
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
Arguments:
|
|
|
|
form_type: A form class that can make this report display things.
|
|
|
|
|
2016-08-25 10:33:19 +00:00
|
|
|
'''
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
def _report(view):
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
@wraps(view)
|
2016-09-01 23:31:12 +00:00
|
|
|
@user_passes_test(views._staff_only)
|
2016-08-26 05:11:08 +00:00
|
|
|
def inner_view(request, *a, **k):
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
form = form_type(request.GET)
|
|
|
|
if form.is_valid() and form.has_changed():
|
|
|
|
report = view(request, form, *a, **k)
|
|
|
|
else:
|
|
|
|
report = None
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
ctx = {
|
|
|
|
"title": title,
|
|
|
|
"form": form,
|
|
|
|
"report": report,
|
|
|
|
}
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
return render(request, "registrasion/report.html", ctx)
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
return inner_view
|
|
|
|
return _report
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-25 11:10:07 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
@report("Paid items", forms.ProductAndCategoryForm)
|
|
|
|
def items_sold(request, form):
|
|
|
|
''' Summarises the items sold and discounts granted for a given set of
|
|
|
|
products, or products from categories. '''
|
2016-08-25 10:33:19 +00:00
|
|
|
|
|
|
|
data = None
|
|
|
|
headings = None
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
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")
|
2016-08-25 11:05:02 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
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"),
|
|
|
|
)
|
2016-08-25 11:05:02 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
print line_items
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
headings = ["Description", "Quantity", "Price", "Total"]
|
2016-08-26 03:40:49 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
data = []
|
|
|
|
total_income = 0
|
|
|
|
for line in line_items:
|
|
|
|
cost = line["total_quantity"] * line["price"]
|
2016-08-26 03:40:49 +00:00
|
|
|
data.append([
|
2016-08-26 05:11:08 +00:00
|
|
|
line["description"], line["total_quantity"],
|
|
|
|
line["price"], cost,
|
2016-08-26 03:40:49 +00:00
|
|
|
])
|
2016-08-26 05:11:08 +00:00
|
|
|
total_income += cost
|
2016-08-25 10:33:19 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
data.append([
|
|
|
|
"(TOTAL)", "--", "--", total_income,
|
|
|
|
])
|
2016-08-26 04:59:54 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
return Report("Paid items", headings, data)
|
2016-08-26 04:59:54 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
|
|
|
|
@report("Inventory", forms.ProductAndCategoryForm)
|
|
|
|
def inventory(request, form):
|
2016-08-26 04:59:54 +00:00
|
|
|
''' Summarises the inventory status of the given items, grouping by
|
|
|
|
invoice status. '''
|
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
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")
|
|
|
|
|
2016-08-26 05:53:33 +00:00
|
|
|
items = items.annotate(
|
|
|
|
is_reserved=Case(
|
|
|
|
When(cart__in=commerce.Cart.reserved_carts(), then=Value(1)),
|
|
|
|
default=Value(0),
|
|
|
|
output_field=models.BooleanField(),
|
|
|
|
),
|
|
|
|
)
|
2016-08-26 05:11:08 +00:00
|
|
|
|
|
|
|
items = items.order_by(
|
|
|
|
"product__category__order",
|
|
|
|
"product__order",
|
|
|
|
).values(
|
|
|
|
"product",
|
|
|
|
"product__category__name",
|
|
|
|
"product__name",
|
|
|
|
).annotate(
|
2016-08-26 05:53:33 +00:00
|
|
|
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),
|
|
|
|
)),
|
2016-08-26 05:11:08 +00:00
|
|
|
)
|
|
|
|
|
2016-08-26 05:53:33 +00:00
|
|
|
headings = [
|
|
|
|
"Product", "Paid", "Reserved", "Unreserved", "Refunded",
|
|
|
|
]
|
2016-08-26 05:11:08 +00:00
|
|
|
data = []
|
|
|
|
|
|
|
|
for item in items:
|
|
|
|
data.append([
|
|
|
|
"%s - %s" % (
|
|
|
|
item["product__category__name"], item["product__name"]
|
|
|
|
),
|
2016-08-26 05:53:33 +00:00
|
|
|
item["total_paid"],
|
|
|
|
item["total_reserved"],
|
|
|
|
item["total_unreserved"],
|
|
|
|
item["total_refunded"],
|
2016-08-26 05:11:08 +00:00
|
|
|
])
|
2016-08-26 04:59:54 +00:00
|
|
|
|
2016-08-26 05:11:08 +00:00
|
|
|
return Report("Inventory", headings, data)
|