Add reports by hour

This commit is contained in:
Gabriel Augendre 2022-04-26 20:08:13 +02:00
parent 0e005cf815
commit ca5e948477
8 changed files with 114 additions and 39 deletions

View file

@ -7,3 +7,4 @@ __pycache__/
**/__pycache__/ **/__pycache__/
.pytest_cache/ .pytest_cache/
.idea/ .idea/
*.mo

View file

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
set -eux set -eux
python manage.py migrate --noinput python manage.py migrate --noinput
inv compilemessages
gunicorn checkout.wsgi -b 0.0.0.0:8000 --log-file - gunicorn checkout.wsgi -b 0.0.0.0:8000 --log-file -

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-26 18:17+0200\n" "POT-Creation-Date: 2022-04-26 20:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -5,7 +5,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-26 18:17+0200\n" "POT-Creation-Date: 2022-04-26 20:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-26 18:17+0200\n" "POT-Creation-Date: 2022-04-26 20:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -46,11 +46,11 @@ msgstr ""
msgid "updated at" msgid "updated at"
msgstr "" msgstr ""
#: purchase/models.py:37 purchase/models.py:66 #: purchase/models.py:37 purchase/models.py:69
msgid "name" msgid "name"
msgstr "" msgstr ""
#: purchase/models.py:42 purchase/models.py:149 #: purchase/models.py:42 purchase/models.py:152
msgid "payment method" msgid "payment method"
msgstr "" msgstr ""
@ -58,52 +58,52 @@ msgstr ""
msgid "payment methods" msgid "payment methods"
msgstr "" msgstr ""
#: purchase/models.py:67 #: purchase/models.py:70
msgid "image" msgid "image"
msgstr "" msgstr ""
#: purchase/models.py:69 #: purchase/models.py:72
msgid "unit price (cents)" msgid "unit price (cents)"
msgstr "" msgstr ""
#: purchase/models.py:69 #: purchase/models.py:72
msgid "unit price in cents" msgid "unit price in cents"
msgstr "" msgstr ""
#: purchase/models.py:72 #: purchase/models.py:75
msgid "display order" msgid "display order"
msgstr "" msgstr ""
#: purchase/models.py:79 purchase/models.py:177 #: purchase/models.py:82 purchase/models.py:180
msgid "product" msgid "product"
msgstr "" msgstr ""
#: purchase/models.py:80 #: purchase/models.py:83
msgid "products" msgid "products"
msgstr "" msgstr ""
#: purchase/models.py:155 purchase/models.py:183 #: purchase/models.py:158 purchase/models.py:186
msgid "basket" msgid "basket"
msgstr "" msgstr ""
#: purchase/models.py:156 #: purchase/models.py:159
msgid "baskets" msgid "baskets"
msgstr "" msgstr ""
#: purchase/models.py:159 #: purchase/models.py:162
#, python-format #, python-format
msgid "Basket #%(id)s" msgid "Basket #%(id)s"
msgstr "" msgstr ""
#: purchase/models.py:185 #: purchase/models.py:188
msgid "quantity" msgid "quantity"
msgstr "" msgstr ""
#: purchase/models.py:190 #: purchase/models.py:193
msgid "basket item" msgid "basket item"
msgstr "" msgstr ""
#: purchase/models.py:191 #: purchase/models.py:194
msgid "basket items" msgid "basket items"
msgstr "" msgstr ""
@ -163,6 +163,7 @@ msgstr ""
#: purchase/templates/purchase/reports.html:14 #: purchase/templates/purchase/reports.html:14
#: purchase/templates/purchase/snippets/report_payment_methods.html:8 #: purchase/templates/purchase/snippets/report_payment_methods.html:8
#: purchase/templates/purchase/snippets/report_products.html:8 #: purchase/templates/purchase/snippets/report_products.html:8
#: purchase/views/reports.py:80
msgid "Turnover" msgid "Turnover"
msgstr "" msgstr ""
@ -170,15 +171,15 @@ msgstr ""
msgid "Average basket" msgid "Average basket"
msgstr "" msgstr ""
#: purchase/templates/purchase/reports.html:27 #: purchase/templates/purchase/reports.html:29
msgid "Products" msgid "Products"
msgstr "" msgstr ""
#: purchase/templates/purchase/reports.html:30 #: purchase/templates/purchase/reports.html:34
msgid "Turnover by payment method" msgid "Turnover by payment method"
msgstr "" msgstr ""
#: purchase/templates/purchase/reports.html:33 #: purchase/templates/purchase/reports.html:37
msgid "Baskets without payment method" msgid "Baskets without payment method"
msgstr "" msgstr ""
@ -203,6 +204,7 @@ msgid "Product"
msgstr "" msgstr ""
#: purchase/templates/purchase/snippets/report_products.html:7 #: purchase/templates/purchase/snippets/report_products.html:7
#: purchase/views/reports.py:64
msgid "# sold" msgid "# sold"
msgstr "" msgstr ""
@ -217,3 +219,11 @@ msgstr ""
#: purchase/views/basket.py:45 #: purchase/views/basket.py:45
msgid "Basket successfully deleted." msgid "Basket successfully deleted."
msgstr "" msgstr ""
#: purchase/views/reports.py:112
msgid "Basket count by hour"
msgstr ""
#: purchase/views/reports.py:117
msgid "Turnover by hour"
msgstr ""

View file

@ -5,7 +5,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-26 18:17+0200\n" "POT-Creation-Date: 2022-04-26 20:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -43,11 +43,11 @@ msgstr "créé à"
msgid "updated at" msgid "updated at"
msgstr "mis à jour à" msgstr "mis à jour à"
#: purchase/models.py:37 purchase/models.py:66 #: purchase/models.py:37 purchase/models.py:69
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
#: purchase/models.py:42 purchase/models.py:149 #: purchase/models.py:42 purchase/models.py:152
msgid "payment method" msgid "payment method"
msgstr "moyen de paiement" msgstr "moyen de paiement"
@ -55,52 +55,52 @@ msgstr "moyen de paiement"
msgid "payment methods" msgid "payment methods"
msgstr "moyens de paiement" msgstr "moyens de paiement"
#: purchase/models.py:67 #: purchase/models.py:70
msgid "image" msgid "image"
msgstr "image" msgstr "image"
#: purchase/models.py:69 #: purchase/models.py:72
msgid "unit price (cents)" msgid "unit price (cents)"
msgstr "prix unitaire (centimes)" msgstr "prix unitaire (centimes)"
#: purchase/models.py:69 #: purchase/models.py:72
msgid "unit price in cents" msgid "unit price in cents"
msgstr "prix unitaire en centimes" msgstr "prix unitaire en centimes"
#: purchase/models.py:72 #: purchase/models.py:75
msgid "display order" msgid "display order"
msgstr "ordre d'affichage" msgstr "ordre d'affichage"
#: purchase/models.py:79 purchase/models.py:177 #: purchase/models.py:82 purchase/models.py:180
msgid "product" msgid "product"
msgstr "produit" msgstr "produit"
#: purchase/models.py:80 #: purchase/models.py:83
msgid "products" msgid "products"
msgstr "produits" msgstr "produits"
#: purchase/models.py:155 purchase/models.py:183 #: purchase/models.py:158 purchase/models.py:186
msgid "basket" msgid "basket"
msgstr "panier" msgstr "panier"
#: purchase/models.py:156 #: purchase/models.py:159
msgid "baskets" msgid "baskets"
msgstr "paniers" msgstr "paniers"
#: purchase/models.py:159 #: purchase/models.py:162
#, python-format #, python-format
msgid "Basket #%(id)s" msgid "Basket #%(id)s"
msgstr "Panier n°%(id)s" msgstr "Panier n°%(id)s"
#: purchase/models.py:185 #: purchase/models.py:188
msgid "quantity" msgid "quantity"
msgstr "quantité" msgstr "quantité"
#: purchase/models.py:190 #: purchase/models.py:193
msgid "basket item" msgid "basket item"
msgstr "article de panier" msgstr "article de panier"
#: purchase/models.py:191 #: purchase/models.py:194
msgid "basket items" msgid "basket items"
msgstr "articles de panier" msgstr "articles de panier"
@ -160,6 +160,7 @@ msgstr "Par jour"
#: purchase/templates/purchase/reports.html:14 #: purchase/templates/purchase/reports.html:14
#: purchase/templates/purchase/snippets/report_payment_methods.html:8 #: purchase/templates/purchase/snippets/report_payment_methods.html:8
#: purchase/templates/purchase/snippets/report_products.html:8 #: purchase/templates/purchase/snippets/report_products.html:8
#: purchase/views/reports.py:80
msgid "Turnover" msgid "Turnover"
msgstr "Chiffre d'affaires" msgstr "Chiffre d'affaires"
@ -167,15 +168,15 @@ msgstr "Chiffre d'affaires"
msgid "Average basket" msgid "Average basket"
msgstr "Panier moyen" msgstr "Panier moyen"
#: purchase/templates/purchase/reports.html:27 #: purchase/templates/purchase/reports.html:29
msgid "Products" msgid "Products"
msgstr "Produits" msgstr "Produits"
#: purchase/templates/purchase/reports.html:30 #: purchase/templates/purchase/reports.html:34
msgid "Turnover by payment method" msgid "Turnover by payment method"
msgstr "Chiffre d'affaires par moyen de paiement" msgstr "Chiffre d'affaires par moyen de paiement"
#: purchase/templates/purchase/reports.html:33 #: purchase/templates/purchase/reports.html:37
msgid "Baskets without payment method" msgid "Baskets without payment method"
msgstr "Paniers sans moyen de paiement" msgstr "Paniers sans moyen de paiement"
@ -200,6 +201,7 @@ msgid "Product"
msgstr "Produit" msgstr "Produit"
#: purchase/templates/purchase/snippets/report_products.html:7 #: purchase/templates/purchase/snippets/report_products.html:7
#: purchase/views/reports.py:64
msgid "# sold" msgid "# sold"
msgstr "Nb. vendus" msgstr "Nb. vendus"
@ -215,6 +217,14 @@ msgstr "Panier correctement modifié."
msgid "Basket successfully deleted." msgid "Basket successfully deleted."
msgstr "Panier correctement supprimé." msgstr "Panier correctement supprimé."
#: purchase/views/reports.py:112
msgid "Basket count by hour"
msgstr "Nombre de paniers par heure"
#: purchase/views/reports.py:117
msgid "Turnover by hour"
msgstr "Chiffre d'affaires par heure"
#, python-format #, python-format
#~ msgid "Total turnover: %(total|currency)s" #~ msgid "Total turnover: %(total|currency)s"
#~ msgstr "Chiffre d'affaires total : %(total|currency)s" #~ msgstr "Chiffre d'affaires total : %(total|currency)s"

View file

@ -24,6 +24,8 @@
{% endfor %} {% endfor %}
</ul> </ul>
{{ by_hour_plot|safe }}
<h2>{% translate "Products" %}</h2> <h2>{% translate "Products" %}</h2>
{% include "purchase/snippets/report_products.html" %} {% include "purchase/snippets/report_products.html" %}
{{ products_sold_plot|safe }} {{ products_sold_plot|safe }}

View file

@ -1,3 +1,4 @@
import datetime
from io import StringIO from io import StringIO
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -5,7 +6,13 @@ from django.views.generic import TemplateView
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from matplotlib import ticker from matplotlib import ticker
from purchase.models import Basket, PaymentMethod, Product, ProductQuerySet from purchase.models import (
Basket,
BasketQuerySet,
PaymentMethod,
Product,
ProductQuerySet,
)
from purchase.views.utils import ProtectedViewsMixin from purchase.views.utils import ProtectedViewsMixin
@ -26,6 +33,8 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
products = Product.objects.with_turnover().with_sold() products = Product.objects.with_turnover().with_sold()
products_sold_plot = self.get_products_sold_plot(products) products_sold_plot = self.get_products_sold_plot(products)
products_turnover_plot = self.get_products_turnover_plot(products) products_turnover_plot = self.get_products_turnover_plot(products)
baskets = list(Basket.objects.priced().order_by("created_at"))
by_hour_plot = self.by_hour_plot(baskets)
context.update( context.update(
{ {
"turnover": Basket.objects.turnover(), "turnover": Basket.objects.turnover(),
@ -35,6 +44,7 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
"products": products, "products": products,
"products_sold_plot": products_sold_plot, "products_sold_plot": products_sold_plot,
"products_turnover_plot": products_turnover_plot, "products_turnover_plot": products_turnover_plot,
"by_hour_plot": by_hour_plot,
"payment_methods": PaymentMethod.objects.with_turnover().with_sold(), "payment_methods": PaymentMethod.objects.with_turnover().with_sold(),
"no_payment_method": Basket.objects.no_payment_method().priced(), "no_payment_method": Basket.objects.no_payment_method().priced(),
} }
@ -71,3 +81,44 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
fig.savefig(image_data, format="svg") fig.savefig(image_data, format="svg")
image_data.seek(0) image_data.seek(0)
return image_data.getvalue() return image_data.getvalue()
def by_hour_plot(self, baskets):
current: datetime.datetime = baskets[0].created_at
current = current.replace(minute=0, second=0, microsecond=0)
end: datetime.datetime = baskets[-1].created_at
basket_index = 0
labels = []
counts = []
turnovers = []
while current < end:
end_slot = current + datetime.timedelta(hours=1)
basket = baskets[basket_index]
count = 0
turnover = 0
while basket.created_at < end_slot and basket_index < len(baskets) - 1:
count += 1
turnover += basket.price / 100
basket_index += 1
basket = baskets[basket_index]
labels.append(current)
counts.append(count)
turnovers.append(turnover)
current = end_slot
fig, ax1 = plt.subplots()
hours_in_day = 24
color = "tab:orange"
ax1.bar(labels, counts, width=1 / hours_in_day, color=color)
ax1.tick_params(axis="x", rotation=15)
ax1.set_ylabel(_("Basket count by hour"), color=color)
color = "tab:blue"
ax2 = ax1.twinx()
ax2.bar(labels, turnovers, width=1 / (hours_in_day * 2), color=color)
ax2.set_ylabel(_("Turnover by hour"), color=color)
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f"))
fig.tight_layout()
image_data = StringIO()
fig.savefig(image_data, format="svg")
image_data.seek(0)
return image_data.getvalue()