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__/
.pytest_cache/
.idea/
*.mo

View file

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

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -46,11 +46,11 @@ msgstr ""
msgid "updated at"
msgstr ""
#: purchase/models.py:37 purchase/models.py:66
#: purchase/models.py:37 purchase/models.py:69
msgid "name"
msgstr ""
#: purchase/models.py:42 purchase/models.py:149
#: purchase/models.py:42 purchase/models.py:152
msgid "payment method"
msgstr ""
@ -58,52 +58,52 @@ msgstr ""
msgid "payment methods"
msgstr ""
#: purchase/models.py:67
#: purchase/models.py:70
msgid "image"
msgstr ""
#: purchase/models.py:69
#: purchase/models.py:72
msgid "unit price (cents)"
msgstr ""
#: purchase/models.py:69
#: purchase/models.py:72
msgid "unit price in cents"
msgstr ""
#: purchase/models.py:72
#: purchase/models.py:75
msgid "display order"
msgstr ""
#: purchase/models.py:79 purchase/models.py:177
#: purchase/models.py:82 purchase/models.py:180
msgid "product"
msgstr ""
#: purchase/models.py:80
#: purchase/models.py:83
msgid "products"
msgstr ""
#: purchase/models.py:155 purchase/models.py:183
#: purchase/models.py:158 purchase/models.py:186
msgid "basket"
msgstr ""
#: purchase/models.py:156
#: purchase/models.py:159
msgid "baskets"
msgstr ""
#: purchase/models.py:159
#: purchase/models.py:162
#, python-format
msgid "Basket #%(id)s"
msgstr ""
#: purchase/models.py:185
#: purchase/models.py:188
msgid "quantity"
msgstr ""
#: purchase/models.py:190
#: purchase/models.py:193
msgid "basket item"
msgstr ""
#: purchase/models.py:191
#: purchase/models.py:194
msgid "basket items"
msgstr ""
@ -163,6 +163,7 @@ msgstr ""
#: purchase/templates/purchase/reports.html:14
#: purchase/templates/purchase/snippets/report_payment_methods.html:8
#: purchase/templates/purchase/snippets/report_products.html:8
#: purchase/views/reports.py:80
msgid "Turnover"
msgstr ""
@ -170,15 +171,15 @@ msgstr ""
msgid "Average basket"
msgstr ""
#: purchase/templates/purchase/reports.html:27
#: purchase/templates/purchase/reports.html:29
msgid "Products"
msgstr ""
#: purchase/templates/purchase/reports.html:30
#: purchase/templates/purchase/reports.html:34
msgid "Turnover by payment method"
msgstr ""
#: purchase/templates/purchase/reports.html:33
#: purchase/templates/purchase/reports.html:37
msgid "Baskets without payment method"
msgstr ""
@ -203,6 +204,7 @@ msgid "Product"
msgstr ""
#: purchase/templates/purchase/snippets/report_products.html:7
#: purchase/views/reports.py:64
msgid "# sold"
msgstr ""
@ -217,3 +219,11 @@ msgstr ""
#: purchase/views/basket.py:45
msgid "Basket successfully deleted."
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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -43,11 +43,11 @@ msgstr "créé à"
msgid "updated at"
msgstr "mis à jour à"
#: purchase/models.py:37 purchase/models.py:66
#: purchase/models.py:37 purchase/models.py:69
msgid "name"
msgstr "nom"
#: purchase/models.py:42 purchase/models.py:149
#: purchase/models.py:42 purchase/models.py:152
msgid "payment method"
msgstr "moyen de paiement"
@ -55,52 +55,52 @@ msgstr "moyen de paiement"
msgid "payment methods"
msgstr "moyens de paiement"
#: purchase/models.py:67
#: purchase/models.py:70
msgid "image"
msgstr "image"
#: purchase/models.py:69
#: purchase/models.py:72
msgid "unit price (cents)"
msgstr "prix unitaire (centimes)"
#: purchase/models.py:69
#: purchase/models.py:72
msgid "unit price in cents"
msgstr "prix unitaire en centimes"
#: purchase/models.py:72
#: purchase/models.py:75
msgid "display order"
msgstr "ordre d'affichage"
#: purchase/models.py:79 purchase/models.py:177
#: purchase/models.py:82 purchase/models.py:180
msgid "product"
msgstr "produit"
#: purchase/models.py:80
#: purchase/models.py:83
msgid "products"
msgstr "produits"
#: purchase/models.py:155 purchase/models.py:183
#: purchase/models.py:158 purchase/models.py:186
msgid "basket"
msgstr "panier"
#: purchase/models.py:156
#: purchase/models.py:159
msgid "baskets"
msgstr "paniers"
#: purchase/models.py:159
#: purchase/models.py:162
#, python-format
msgid "Basket #%(id)s"
msgstr "Panier n°%(id)s"
#: purchase/models.py:185
#: purchase/models.py:188
msgid "quantity"
msgstr "quantité"
#: purchase/models.py:190
#: purchase/models.py:193
msgid "basket item"
msgstr "article de panier"
#: purchase/models.py:191
#: purchase/models.py:194
msgid "basket items"
msgstr "articles de panier"
@ -160,6 +160,7 @@ msgstr "Par jour"
#: purchase/templates/purchase/reports.html:14
#: purchase/templates/purchase/snippets/report_payment_methods.html:8
#: purchase/templates/purchase/snippets/report_products.html:8
#: purchase/views/reports.py:80
msgid "Turnover"
msgstr "Chiffre d'affaires"
@ -167,15 +168,15 @@ msgstr "Chiffre d'affaires"
msgid "Average basket"
msgstr "Panier moyen"
#: purchase/templates/purchase/reports.html:27
#: purchase/templates/purchase/reports.html:29
msgid "Products"
msgstr "Produits"
#: purchase/templates/purchase/reports.html:30
#: purchase/templates/purchase/reports.html:34
msgid "Turnover by payment method"
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"
msgstr "Paniers sans moyen de paiement"
@ -200,6 +201,7 @@ msgid "Product"
msgstr "Produit"
#: purchase/templates/purchase/snippets/report_products.html:7
#: purchase/views/reports.py:64
msgid "# sold"
msgstr "Nb. vendus"
@ -215,6 +217,14 @@ msgstr "Panier correctement modifié."
msgid "Basket successfully deleted."
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
#~ msgid "Total turnover: %(total|currency)s"
#~ msgstr "Chiffre d'affaires total : %(total|currency)s"

View file

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

View file

@ -1,3 +1,4 @@
import datetime
from io import StringIO
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 ticker
from purchase.models import Basket, PaymentMethod, Product, ProductQuerySet
from purchase.models import (
Basket,
BasketQuerySet,
PaymentMethod,
Product,
ProductQuerySet,
)
from purchase.views.utils import ProtectedViewsMixin
@ -26,6 +33,8 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
products = Product.objects.with_turnover().with_sold()
products_sold_plot = self.get_products_sold_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(
{
"turnover": Basket.objects.turnover(),
@ -35,6 +44,7 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
"products": products,
"products_sold_plot": products_sold_plot,
"products_turnover_plot": products_turnover_plot,
"by_hour_plot": by_hour_plot,
"payment_methods": PaymentMethod.objects.with_turnover().with_sold(),
"no_payment_method": Basket.objects.no_payment_method().priced(),
}
@ -71,3 +81,44 @@ class ReportsView(ProtectedViewsMixin, TemplateView):
fig.savefig(image_data, format="svg")
image_data.seek(0)
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()