From 8138aa9af1303f551d99888991b257ea269d110d Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 7 Aug 2021 23:19:14 +0200 Subject: [PATCH] feat: Add statistics for deposits app --- deposits/locale/fr/LC_MESSAGES/django.po | 79 +++++++++---- deposits/templates/deposits/aff_stats.html | 40 +++++++ deposits/templates/deposits/index_stats.html | 36 ++++++ deposits/templates/deposits/navbar.html | 4 + deposits/urls.py | 1 + deposits/views.py | 115 ++++++++++++++++++- 6 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 deposits/templates/deposits/aff_stats.html create mode 100644 deposits/templates/deposits/index_stats.html diff --git a/deposits/locale/fr/LC_MESSAGES/django.po b/deposits/locale/fr/LC_MESSAGES/django.po index 5202ea29..11267cf4 100644 --- a/deposits/locale/fr/LC_MESSAGES/django.po +++ b/deposits/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-08-07 18:27+0200\n" +"POT-Creation-Date: 2021-08-07 23:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -32,10 +32,12 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: deposits/forms.py:44 deposits/templates/deposits/aff_deposit_item.html:31 +#: deposits/views.py:301 msgid "Deposit item" msgstr "Article (caution)" #: deposits/forms.py:45 deposits/templates/deposits/aff_deposits.html:48 +#: deposits/views.py:272 msgid "Payment method" msgstr "Moyen de paiement" @@ -59,7 +61,7 @@ msgstr "rendue" msgid "deposit amount" msgstr "montant de la caution" -#: deposits/models.py:60 deposits/views.py:114 +#: deposits/models.py:60 deposits/views.py:116 msgid "deposit" msgstr "caution" @@ -98,7 +100,8 @@ msgid "deposit items" msgstr "articles (cautions)" #: deposits/templates/deposits/aff_deposit_item.html:32 -#: deposits/templates/deposits/aff_deposits.html:44 +#: deposits/templates/deposits/aff_deposits.html:44 deposits/views.py:276 +#: deposits/views.py:305 deposits/views.py:338 msgid "Amount" msgstr "Montant" @@ -144,7 +147,7 @@ msgstr "" "Attention: voulez-vous vraiment supprimer cet objet %(objet_name)s " "( %(objet)s ) ?" -#: deposits/templates/deposits/delete.html:36 deposits/views.py:70 +#: deposits/templates/deposits/delete.html:36 deposits/views.py:72 msgid "Confirm" msgstr "Confirmer" @@ -152,7 +155,7 @@ msgstr "Confirmer" msgid "Create or edit deposit" msgstr "Créer ou modifier une caution" -#: deposits/templates/deposits/deposit.html:35 deposits/views.py:173 +#: deposits/templates/deposits/deposit.html:35 deposits/views.py:179 msgid "Add" msgstr "Ajouter" @@ -189,66 +192,102 @@ msgid_plural "Unreturned deposits" msgstr[0] "Caution non rendue" msgstr[1] "Cautions non rendues" -#: deposits/templates/deposits/index_deposits.html:46 +#: deposits/templates/deposits/index_deposits.html:45 deposits/views.py:274 +#: deposits/views.py:303 msgid "Unreturned deposits" msgstr "Cautions non rendues" -#: deposits/templates/deposits/index_deposits.html:57 +#: deposits/templates/deposits/index_deposits.html:56 deposits/views.py:273 +#: deposits/views.py:302 msgid "Returned deposits" msgstr "Cautions rendues" +#: deposits/templates/deposits/index_stats.html:28 +#: deposits/templates/deposits/index_stats.html:31 +#: deposits/templates/deposits/navbar.html:36 +msgid "Deposits statistics" +msgstr "Statistiques sur les cautions" + #: deposits/templates/deposits/navbar.html:30 msgid "View deposits" msgstr "Voir les cautions" -#: deposits/views.py:64 +#: deposits/views.py:66 msgid "The deposit was created." msgstr "La caution a été créée." -#: deposits/views.py:71 +#: deposits/views.py:73 msgid "New deposit" msgstr "Nouvelle caution" -#: deposits/views.py:89 +#: deposits/views.py:91 msgid "The deposit was edited." msgstr "La caution a été modifiée." -#: deposits/views.py:95 deposits/views.py:196 +#: deposits/views.py:97 deposits/views.py:202 msgid "Edit" msgstr "Modifier" -#: deposits/views.py:96 +#: deposits/views.py:98 msgid "Edit deposit" msgstr "Modifier la caution" -#: deposits/views.py:111 +#: deposits/views.py:113 msgid "The deposit was deleted." msgstr "La caution a été supprimée." -#: deposits/views.py:168 +#: deposits/views.py:174 msgid "The item was created." msgstr "L'article a été créé." -#: deposits/views.py:174 +#: deposits/views.py:180 msgid "New deposit item" msgstr "Nouvel article (caution)" -#: deposits/views.py:191 +#: deposits/views.py:197 msgid "The item was edited." msgstr "L'article a été modifié." -#: deposits/views.py:197 +#: deposits/views.py:203 msgid "Edit deposit item" msgstr "Modifier l'article" -#: deposits/views.py:214 +#: deposits/views.py:220 msgid "The items were deleted." msgstr "Les articles ont été supprimés." -#: deposits/views.py:219 +#: deposits/views.py:225 msgid "Delete" msgstr "Supprimer" -#: deposits/views.py:220 +#: deposits/views.py:226 msgid "Delete deposit item" msgstr "Supprimer l'article" + +#: deposits/views.py:270 +msgid "Deposits by payment method" +msgstr "Cautions par moyen de paiement" + +#: deposits/views.py:275 deposits/views.py:304 deposits/views.py:337 +msgid "Total" +msgstr "Total" + +#: deposits/views.py:299 +msgid "Deposits by item type" +msgstr "Cautions par type d'article" + +#: deposits/views.py:321 +msgid "Not yet returned" +msgstr "Pas encore rendues" + +#: deposits/views.py:326 +msgid "Already returned" +msgstr "Déjà rendues" + +#: deposits/views.py:334 +msgid "Deposits amounts" +msgstr "Montants de cautions" + +#: deposits/views.py:336 +msgid "Category" +msgstr "Catégorie" diff --git a/deposits/templates/deposits/aff_stats.html b/deposits/templates/deposits/aff_stats.html new file mode 100644 index 00000000..727e2557 --- /dev/null +++ b/deposits/templates/deposits/aff_stats.html @@ -0,0 +1,40 @@ +{% comment %} +Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2021 Jean-Romain Garnier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +

{{ stats_dict.title }}

+ + + + + {% for header in stats_dict.headers %} + + {% endfor %} + + + {% for stats in stats_dict.data %} + + {% for item in stats %} + + {% endfor %} + + {% endfor %} +
{{ header }}
{{ item }}
diff --git a/deposits/templates/deposits/index_stats.html b/deposits/templates/deposits/index_stats.html new file mode 100644 index 00000000..c982c87b --- /dev/null +++ b/deposits/templates/deposits/index_stats.html @@ -0,0 +1,36 @@ +{% extends 'deposits/sidebar.html' %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2021 Jean-Romain Garnier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load i18n %} + +{% block title %}{% trans "Deposits statistics" %}{% endblock %} + +{% block content %} +

{% trans "Deposits statistics" %}

+ +{% for stats_dict in stats_list %} + {% include 'deposits/aff_stats.html' with stats_dict=stats_dict %} +
+{% endfor %} + +{% endblock %} diff --git a/deposits/templates/deposits/navbar.html b/deposits/templates/deposits/navbar.html index d704237b..c707857f 100644 --- a/deposits/templates/deposits/navbar.html +++ b/deposits/templates/deposits/navbar.html @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load i18n %} +
  • {% trans "Deposits" %} » @@ -31,5 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
  • {% trans "Deposit items" %}
  • +
  • + {% trans "Deposits statistics" %}
  • diff --git a/deposits/urls.py b/deposits/urls.py index c56099d6..898f7481 100644 --- a/deposits/urls.py +++ b/deposits/urls.py @@ -41,4 +41,5 @@ urlpatterns = [ ), path("del_deposit_item", views.del_deposit_item, name="del-deposit-item"), path("index_deposit_item", views.index_deposit_item, name="index-deposit-item"), + path("index_stats", views.index_stats, name="index-stats"), ] diff --git a/deposits/views.py b/deposits/views.py index 86aab85c..1442d27c 100644 --- a/deposits/views.py +++ b/deposits/views.py @@ -28,6 +28,7 @@ from django.shortcuts import redirect, render from django.template.loader import render_to_string from django.urls import reverse from django.utils.translation import ugettext as _ +from django.db.models import Sum from preferences.models import GeneralOption from re2o.acl import ( @@ -40,6 +41,7 @@ from re2o.acl import ( from re2o.base import re2o_paginator from re2o.views import form from users.models import User +from cotisations.models import Paiement from .forms import DepositForm, DepositItemForm, DelDepositItemForm from .models import Deposit, DepositItem @@ -118,7 +120,7 @@ def del_deposit(request, deposit, **_kwargs): @login_required -@can_view_all(Deposit) +@can_view_all(Deposit, DepositItem, Paiement, User) def index_deposits(request): """ View used to display the list of all deposits. @@ -239,6 +241,117 @@ def index_deposit_item(request): return render(request, "deposits/index_deposit_item.html", {"item_list": item_list}) +@login_required +@can_view_all(Deposit, DepositItem, Paiement, User) +def index_stats(request): + """ + View used to display general statistics about deposits + """ + # We want to build a list of tables for statistics + stats = [] + + # Statistics for payment methods + payment_data = [] + for method in Paiement.objects.order_by("moyen"): + deposits = Deposit.objects.filter(payment_method=method) + amount = deposits.aggregate(Sum("deposit_amount")).get( + "deposit_amount__sum", None + ) + payment_data.append( + ( + method.moyen, + deposits.filter(returned=False).count(), + deposits.filter(returned=True).count(), + deposits.count(), + "{} €".format(amount or 0), + ) + ) + + stats.append( + { + "title": _("Deposits by payment method"), + "headers": [ + _("Payment method"), + _("Returned deposits"), + _("Unreturned deposits"), + _("Total"), + _("Amount"), + ], + "data": payment_data, + } + ) + + # Statistics for deposit items + items_data = [] + for item in DepositItem.objects.order_by("name"): + deposits = Deposit.objects.filter(item=item) + amount = deposits.aggregate(Sum("deposit_amount")).get( + "deposit_amount__sum", None + ) + items_data.append( + ( + item.name, + deposits.filter(returned=False).count(), + deposits.filter(returned=True).count(), + deposits.count(), + "{} €".format(amount or 0), + ) + ) + + stats.append( + { + "title": _("Deposits by item type"), + "headers": [ + _("Deposit item"), + _("Returned deposits"), + _("Unreturned deposits"), + _("Total"), + _("Amount"), + ], + "data": items_data, + } + ) + + # Statistics for amounts + pending_amount = ( + Deposit.objects.filter(returned=False) + .aggregate(Sum("deposit_amount")) + .get("deposit_amount__sum", None) + ) + reimbursed_amount = ( + Deposit.objects.filter(returned=True) + .aggregate(Sum("deposit_amount")) + .get("deposit_amount__sum", None) + ) + + amounts_data = [ + ( + _("Not yet returned"), + Deposit.objects.filter(returned=False).count(), + "{} €".format(pending_amount or 0), + ), + ( + _("Already returned"), + Deposit.objects.filter(returned=True).count(), + "{} €".format(reimbursed_amount or 0), + ), + ] + + stats.append( + { + "title": _("Deposits amounts"), + "headers": [ + _("Category"), + _("Total"), + _("Amount"), + ], + "data": amounts_data, + } + ) + + return render(request, "deposits/index_stats.html", {"stats_list": stats}) + + # Canonic views for optional apps def aff_profil(request, user): """View used to display the deposits on a user's profile."""