- Price: {{ object.price_display }}
+ Price: {{ object.price|currency }}
@@ -9,7 +10,7 @@
Basket #{{ basket.id }}
{{ basket.items.count }} items
- {{ basket.price_display }}
+ {{ basket.price|currency }}
{{ basket.payment_method }}
{% if perms.purchase.change_basket %}
diff --git a/src/purchase/templates/purchase/reports.html b/src/purchase/templates/purchase/reports.html
new file mode 100644
index 0000000..fbf22a9
--- /dev/null
+++ b/src/purchase/templates/purchase/reports.html
@@ -0,0 +1,19 @@
+{% extends "common/base.html" %}
+{% load purchase %}
+
+{% block content %}
+
Reports
+
Total turnover
+
+ -
+ {{ total|currency }}
+
+
+
Turnover by day
+
+
Products
+ {% include "purchase/snippets/report_products.html" %}
+
Turnover by product by day
+
Turnover by payment method
+
Turnover by payment method by day
+{% endblock %}
diff --git a/src/purchase/templates/purchase/basket_item.html b/src/purchase/templates/purchase/snippets/basket_item.html
similarity index 100%
rename from src/purchase/templates/purchase/basket_item.html
rename to src/purchase/templates/purchase/snippets/basket_item.html
diff --git a/src/purchase/templates/purchase/snippets/report_products.html b/src/purchase/templates/purchase/snippets/report_products.html
new file mode 100644
index 0000000..6661525
--- /dev/null
+++ b/src/purchase/templates/purchase/snippets/report_products.html
@@ -0,0 +1,18 @@
+{% load purchase %}
+
+
+
+ Product |
+ Sold |
+ Turnover |
+
+
+ {% for product in products %}
+
+ {{ product }} |
+ {{ product.sold }} |
+ {{ product.turnover|currency }} |
+
+ {% endfor %}
+
+
diff --git a/src/purchase/templatetags/__init__.py b/src/purchase/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/purchase/templatetags/purchase.py b/src/purchase/templatetags/purchase.py
new file mode 100644
index 0000000..4d5266a
--- /dev/null
+++ b/src/purchase/templatetags/purchase.py
@@ -0,0 +1,10 @@
+from django import template
+
+register = template.Library()
+
+
+@register.filter
+def currency(value):
+ if isinstance(value, int) or isinstance(value, float):
+ return f"{value/100:.2f}€"
+ return value
diff --git a/src/purchase/urls.py b/src/purchase/urls.py
index 622298e..e7531d8 100644
--- a/src/purchase/urls.py
+++ b/src/purchase/urls.py
@@ -6,6 +6,7 @@ from purchase.views import (
NewBasketView,
UpdateBasketView,
)
+from purchase.views.reports import ReportsView
app_name = "purchase"
urlpatterns = [
@@ -13,4 +14,5 @@ urlpatterns = [
path("new/", NewBasketView.as_view(), name="new"),
path("
/update/", UpdateBasketView.as_view(), name="update"),
path("/delete/", DeleteBasketView.as_view(), name="delete"),
+ path("reports/", ReportsView.as_view(), name="reports"),
]
diff --git a/src/purchase/views/__init__.py b/src/purchase/views/__init__.py
new file mode 100644
index 0000000..2758c31
--- /dev/null
+++ b/src/purchase/views/__init__.py
@@ -0,0 +1,3 @@
+from .basket import DeleteBasketView, ListBasketsView, NewBasketView, UpdateBasketView
+
+__all__ = ["NewBasketView", "UpdateBasketView", "DeleteBasketView", "ListBasketsView"]
diff --git a/src/purchase/views.py b/src/purchase/views/basket.py
similarity index 86%
rename from src/purchase/views.py
rename to src/purchase/views/basket.py
index b356ac0..41ab5b8 100644
--- a/src/purchase/views.py
+++ b/src/purchase/views/basket.py
@@ -1,14 +1,10 @@
-from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from purchase.forms import BasketForm
from purchase.models import Basket
-
-
-class ProtectedViewsMixin(PermissionRequiredMixin, LoginRequiredMixin):
- pass
+from purchase.views.utils import ProtectedViewsMixin
class NewBasketView(ProtectedViewsMixin, SuccessMessageMixin, CreateView):
@@ -17,6 +13,8 @@ class NewBasketView(ProtectedViewsMixin, SuccessMessageMixin, CreateView):
form_class = BasketForm
success_message = "Successfully created basket."
+ queryset = Basket.objects.priced()
+
def get_success_url(self):
if self.request.user.has_perm("purchase.change_basket"):
return super().get_success_url()
@@ -29,6 +27,7 @@ class UpdateBasketView(ProtectedViewsMixin, SuccessMessageMixin, UpdateView):
model = Basket
form_class = BasketForm
success_message = "Successfully updated basket."
+ queryset = Basket.objects.priced()
class ListBasketsView(ProtectedViewsMixin, ListView):
@@ -36,12 +35,14 @@ class ListBasketsView(ProtectedViewsMixin, ListView):
model = Basket
context_object_name = "baskets"
ordering = "-id"
+ queryset = Basket.objects.priced()
class DeleteBasketView(ProtectedViewsMixin, SuccessMessageMixin, DeleteView):
permission_required = ["purchase.delete_basket"]
model = Basket
success_message = "Basket successfully deleted."
+ queryset = Basket.objects.priced()
def get_success_url(self):
return reverse("purchase:list")
diff --git a/src/purchase/views/reports.py b/src/purchase/views/reports.py
new file mode 100644
index 0000000..a0f62d0
--- /dev/null
+++ b/src/purchase/views/reports.py
@@ -0,0 +1,20 @@
+from django.db.models import Sum
+from django.views.generic import TemplateView
+
+from purchase.models import Basket, PaymentMethod, Product
+
+
+class ReportsView(TemplateView):
+ template_name = "purchase/reports.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context.update(
+ {
+ "total": Basket.objects.priced().aggregate(total=Sum("price"))["total"],
+ "by_day": {},
+ "products": Product.objects.with_turnover().with_sold(),
+ "payment_methods": PaymentMethod.objects.with_turnover(),
+ }
+ )
+ return context
diff --git a/src/purchase/views/utils.py b/src/purchase/views/utils.py
new file mode 100644
index 0000000..e12c2f7
--- /dev/null
+++ b/src/purchase/views/utils.py
@@ -0,0 +1,5 @@
+from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
+
+
+class ProtectedViewsMixin(PermissionRequiredMixin, LoginRequiredMixin):
+ pass