diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cdb814..91f5e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## v3.6.0 +* AJout d'un débit direct comme champ du profil +* Suppression des codes bare +* Création plus simple (création automatiques des produits avec les bons prix) +* Calcul des prix des produits depuis le site +* Génération de factures depuis le site +* Ajouter un champ "raison" dans les accès gracieux +* Fix de la recherche dans l'admin +* Onglet de répartition des cotisations +* Ajout d'un champ alcool pour optimiser le classement +* Amélioration et fix de la redirection après connexion +* Amélioration de l'affichage du nombre de jour dans une cotisation +* Amélioration de l'affichage des pressions +* TM (trademarks) enlevés et remplacés ## v3.5.3 * Fix le profil (division par 0 lorsque toutes les transactions d'un produit avaient été annulées) ## v3.5.2 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7b3f981 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Yoann `Nanoy` Pietri + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/coopeV3/settings.py b/coopeV3/settings.py index 4a4abca..7b3448b 100644 --- a/coopeV3/settings.py +++ b/coopeV3/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = [ 'dal_select2', 'simple_history', 'django_tex', + 'debug_toolbar' ] MIDDLEWARE = [ @@ -50,6 +51,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', 'django.contrib.admindocs.middleware.XViewMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', ] ROOT_URLCONF = 'coopeV3.urls' @@ -127,3 +129,4 @@ LOGIN_URL = '/users/login' MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles') MEDIA_URL = '/media/' +INTERNAL_IPS = ["127.0.0.1"] \ No newline at end of file diff --git a/coopeV3/urls.py b/coopeV3/urls.py index a4768a3..b65889a 100644 --- a/coopeV3/urls.py +++ b/coopeV3/urls.py @@ -30,3 +30,10 @@ urlpatterns = [ path('gestion/', include('gestion.urls')), path('preferences/', include('preferences.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + +if settings.DEBUG: + import debug_toolbar + urlpatterns = [ + path('__debug__/', include(debug_toolbar.urls)), + ] + urlpatterns diff --git a/coopeV3/utils.py b/coopeV3/utils.py new file mode 100644 index 0000000..d32e4b4 --- /dev/null +++ b/coopeV3/utils.py @@ -0,0 +1,7 @@ +import math + +def compute_price(price, a, b, c, alpha): + if price < alpha: + return float(price) * (1 + float(a) + float(b) * math.exp(-c/(price-alpha)**2)) + else: + return price * (1 + a) diff --git a/django_tex/core.py b/django_tex/core.py index 6d67d5f..22fdacb 100644 --- a/django_tex/core.py +++ b/django_tex/core.py @@ -18,6 +18,7 @@ def run_tex(source): filename = os.path.join(tempdir, 'texput.tex') with open(filename, 'x', encoding='utf-8') as f: f.write(source) + print(source) latex_interpreter = getattr(settings, 'LATEX_INTERPRETER', DEFAULT_INTERPRETER) latex_command = 'cd "{tempdir}" && {latex_interpreter} -interaction=batchmode {path}'.format(tempdir=tempdir, latex_interpreter=latex_interpreter, path=os.path.basename(filename)) process = run(latex_command, shell=True, stdout=PIPE, stderr=PIPE) diff --git a/gestion/admin.py b/gestion/admin.py index aa5508c..60d2035 100644 --- a/gestion/admin.py +++ b/gestion/admin.py @@ -17,7 +17,7 @@ class ConsumptionHistoryAdmin(SimpleHistoryAdmin): """ list_display = ('customer', 'product', 'quantity', 'paymentMethod', 'date', 'amount') ordering = ('-date', ) - search_fields = ('customer', 'product') + search_fields = ('customer__username', 'customer__first_name', 'customer__last_name', 'product__name') list_filter = ('paymentMethod',) class KegAdmin(SimpleHistoryAdmin): @@ -35,7 +35,7 @@ class KegHistoryAdmin(SimpleHistoryAdmin): """ list_display = ('keg', 'openingDate', 'closingDate', 'isCurrentKegHistory', 'quantitySold') ordering = ('-openingDate', 'quantitySold') - search_fields = ('keg',) + search_fields = ('keg__name',) list_filter = ('isCurrentKegHistory', 'keg') class MenuHistoryAdmin(SimpleHistoryAdmin): @@ -70,7 +70,7 @@ class ReloadAdmin(SimpleHistoryAdmin): """ list_display = ('customer', 'amount', 'date', 'PaymentMethod') ordering = ('-date', 'amount', 'customer') - search_fields = ('customer',) + search_fields = ('customer__username', 'customer__first_name', 'customer__last_name') list_filter = ('PaymentMethod', ) class RefundAdmin(SimpleHistoryAdmin): @@ -79,7 +79,7 @@ class RefundAdmin(SimpleHistoryAdmin): """ list_display = ('customer', 'amount', 'date') ordering = ('-date', 'amount', 'customer') - search_fields = ('customer',) + search_fields = ('customer__username', 'customer__first_name', 'customer__last_name') class CategoryAdmin(SimpleHistoryAdmin): """ diff --git a/gestion/forms.py b/gestion/forms.py index 1d0a56f..daa8b8c 100644 --- a/gestion/forms.py +++ b/gestion/forms.py @@ -1,11 +1,13 @@ from django import forms from django.core.exceptions import ValidationError from django.contrib.auth.models import User +from django.core.validators import MinValueValidator + from dal import autocomplete from .models import Reload, Refund, Product, Keg, Menu, Category -from preferences.models import PaymentMethod +from preferences.models import PaymentMethod, PriceProfile class ReloadForm(forms.ModelForm): """ @@ -44,17 +46,21 @@ class KegForm(forms.ModelForm): """ A form to create and edit a :class:`~gestion.models.Keg`. """ - def __init__(self, *args, **kwargs): - super(KegForm, self).__init__(*args, **kwargs) - self.fields['pinte'].queryset = Product.objects.filter(draft_category=Product.DRAFT_PINTE) - self.fields['demi'].queryset = Product.objects.filter(draft_category=Product.DRAFT_DEMI) - self.fields['galopin'].queryset = Product.objects.filter(draft_category=Product.DRAFT_GALOPIN) class Meta: model = Keg - fields = "__all__" + fields = ["name", "stockHold", "amount", "capacity"] widgets = {'amount': forms.TextInput} + category = forms.ModelChoiceField(queryset=Category.objects.all(), label="Catégorie") + deg = forms.DecimalField(max_digits=5, decimal_places=2, label="Degré", validators=[MinValueValidator(0)]) + create_galopin = forms.BooleanField(label="Créer le produit galopin ?") + + def clean(self): + cleaned_data = super().clean() + if cleaned_data.get("name")[0:4] != "Fût ": + raise ValidationError("Le nom du fût doit être sous la forme 'Fût nom de la bière'") + class MenuForm(forms.ModelForm): """ A form to create and edit a :class:`~gestion.models.Menu`. @@ -122,4 +128,25 @@ class SearchCategoryForm(forms.Form): """ A form to search a :class:`~gestion.models.Category`. """ - category = forms.ModelChoiceField(queryset=Category.objects.all(), required=True, label="Catégorie", widget=autocomplete.ModelSelect2(url='gestion:categories-autocomplete', attrs={'data-minimum-input-length':2})) \ No newline at end of file + category = forms.ModelChoiceField(queryset=Category.objects.all(), required=True, label="Catégorie", widget=autocomplete.ModelSelect2(url='gestion:categories-autocomplete', attrs={'data-minimum-input-length':2})) + +class GenerateInvoiceForm(forms.Form): + """ + A form to generate an invoice + """ + invoice_date = forms.CharField(label="Date") + invoice_number = forms.CharField(label="Numéro", help_text="Au format 19018, sans le FE") + invoice_place = forms.CharField(label="Lieu") + invoice_object = forms.CharField(label="Objet") + invoice_description = forms.CharField(label="Description", required=False) + client_name = forms.CharField(label="Nom du client") + client_address_fisrt_line = forms.CharField(label="Première ligne d'adresse") + client_address_second_line = forms.CharField(label="Deuxième ligne d'adresse") + products = forms.CharField(widget=forms.Textarea, label="Produits", help_text="Au format nom;prix;quantité avec saut de ligne") + +class ComputePriceForm(forms.Form): + """ + A form to compute price + """ + price_profile = forms.ModelChoiceField(queryset=PriceProfile.objects.all(), label="Profil de prix") + price = forms.DecimalField(max_digits=10, decimal_places=5, label="Prix") \ No newline at end of file diff --git a/gestion/migrations/0010_auto_20190623_1623.py b/gestion/migrations/0010_auto_20190623_1623.py new file mode 100644 index 0000000..c134006 --- /dev/null +++ b/gestion/migrations/0010_auto_20190623_1623.py @@ -0,0 +1,43 @@ +# Generated by Django 2.1 on 2019-06-23 14:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0009_auto_20190506_0939'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalkeg', + name='name', + field=models.CharField(db_index=True, max_length=255, verbose_name='Nom'), + ), + migrations.AlterField( + model_name='historicalproduct', + name='barcode', + field=models.CharField(db_index=True, max_length=255, verbose_name='Code barre'), + ), + migrations.AlterField( + model_name='historicalproduct', + name='name', + field=models.CharField(db_index=True, max_length=255, verbose_name='Nom'), + ), + migrations.AlterField( + model_name='keg', + name='name', + field=models.CharField(max_length=255, unique=True, verbose_name='Nom'), + ), + migrations.AlterField( + model_name='product', + name='barcode', + field=models.CharField(max_length=255, unique=True, verbose_name='Code barre'), + ), + migrations.AlterField( + model_name='product', + name='name', + field=models.CharField(max_length=255, unique=True, verbose_name='Nom'), + ), + ] diff --git a/gestion/migrations/0011_auto_20190623_1640.py b/gestion/migrations/0011_auto_20190623_1640.py new file mode 100644 index 0000000..dfc3f47 --- /dev/null +++ b/gestion/migrations/0011_auto_20190623_1640.py @@ -0,0 +1,37 @@ +# Generated by Django 2.1 on 2019-06-23 14:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0010_auto_20190623_1623'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicalkeg', + name='barcode', + ), + migrations.RemoveField( + model_name='historicalmenu', + name='barcode', + ), + migrations.RemoveField( + model_name='historicalproduct', + name='barcode', + ), + migrations.RemoveField( + model_name='keg', + name='barcode', + ), + migrations.RemoveField( + model_name='menu', + name='barcode', + ), + migrations.RemoveField( + model_name='product', + name='barcode', + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 9d70830..06a4559 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -46,7 +46,7 @@ class Product(models.Model): class Meta: verbose_name = "Produit" - name = models.CharField(max_length=40, verbose_name="Nom", unique=True) + name = models.CharField(max_length=255, verbose_name="Nom", unique=True) """ The name of the product. """ @@ -62,10 +62,6 @@ class Product(models.Model): """ Number of product at the bar. """ - barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre") - """ - The barcode of the product. - """ category = models.ForeignKey('Category', on_delete=models.PROTECT, verbose_name="Catégorie") """ The category of the product @@ -98,7 +94,11 @@ class Product(models.Model): history = HistoricalRecords() def __str__(self): - return self.name + if self.draft_category == self.DRAFT_NONE: + return self.name + "(" + str(self.amount) + " €)" + else: + return self.name + " (" + str(self.amount) + " €, " + str(self.deg) + "°)" + def user_ranking(self, pk): """ @@ -158,7 +158,7 @@ class Keg(models.Model): ("close_keg", "Peut fermer les fûts") ) - name = models.CharField(max_length=20, unique=True, verbose_name="Nom") + name = models.CharField(max_length=255, unique=True, verbose_name="Nom") """ The name of the keg. """ @@ -166,10 +166,6 @@ class Keg(models.Model): """ The number of this keg in the hold. """ - barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre") - """ - The barcode of the keg. - """ amount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name="Prix du fût", validators=[MinValueValidator(0)]) """ The price of the keg. @@ -313,10 +309,6 @@ class Menu(models.Model): """ Price of the menu. """ - barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre") - """ - Barcode of the menu. - """ articles = models.ManyToManyField(Product, verbose_name="Produits") """ Stores :class:`Products ` contained in the menu diff --git a/gestion/templates/gestion/divide.html b/gestion/templates/gestion/divide.html new file mode 100644 index 0000000..f4d3465 --- /dev/null +++ b/gestion/templates/gestion/divide.html @@ -0,0 +1,77 @@ +{% extends 'base.html' %} +{% block entete %}Répartition des cotisations{% endblock %} +{% block navbar %} + +{% endblock %} +{% block content %} +
+
+

Répartition des cotisations

+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ChampValeur
Nombre de cotisations non réparties{{total_cotisations}}
Valeur totale des cotisations non réparties{{total_amount}} €
Valeur à donner au Club Phœnix Technopôle Metz{{total_amount_ptm}} €
+
+
+ {% csrf_token %} + +
+

Attention, cliquer sur ce bouton marquera toutes les cotisations actuellement non réparties comme réparties. L'historique de cette action n'est pas simple à obtenir et l'action peut être considérée comme irreversible.

+
+
+
+
+

Historique des répartitions

+
+
+
+ + + + + + + + + + + + {% for divide_history in divide_histories %} + + + + + + + + {% endfor %} + +
DateNombre de cotisationsMontant des cotisationsMontant des cotisations pourle PhœnixCoopeman
{{ divide_history.date }}{{ divide_history.total_cotisations }}{{ divide_history.total_cotisations_amount }} €{{ divide_history.total_ptm_amount }} €{{ divide_history.coopeman }}
+
+
+
+{% endblock %} diff --git a/gestion/templates/gestion/invoice.tex b/gestion/templates/gestion/invoice.tex new file mode 100644 index 0000000..8fd7136 --- /dev/null +++ b/gestion/templates/gestion/invoice.tex @@ -0,0 +1,100 @@ +\documentclass[french,11pt]{article} +\usepackage{babel} +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[a4paper]{geometry} +\usepackage{units} +\usepackage{graphicx} +\usepackage{fancyhdr} +\usepackage{fp} +\usepackage{float} +\usepackage{eurosym} +\def\FactureDate { {{- invoice_date -}} } +\def\FactureNum { {{- invoice_number -}} } +\def\FactureAcquittee {non} +\def\FactureLieu { {{- invoice_place -}} } +\def\FactureObjet { {{- invoice_object -}} } +\def\FactureDescr { +{{- invoice_description -}} +} + +\def\ClientNom{ {{- client_name -}} } +\def\ClientAdresse{ + {{- client_address_first_line -}}\newline + {{ client_address_second_line }} +} + +\geometry{verbose,tmargin=4em,bmargin=8em,lmargin=6em,rmargin=6em} +\setlength{\parindent}{0pt} +\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} + +\thispagestyle{fancy} +\pagestyle{fancy} +\setlength{\parindent}{0pt} + +\renewcommand{\headrulewidth}{0pt} +\cfoot{ + \small{ +Coopé Technopôle Metz (CTM)\\ +Adresse mail : coopemetz@gmail.com\\} + \tiny{ +Inscrite au registre des associations du tribunal d’instance de Metz + } +} + + + +\begin{document} + +\begin{figure}[H] +\includegraphics[scale=0.3]{ {{- path -}} } +\end{figure} +Coopé Technopôle Metz\\ +4 place Édouard Branly\\ +57070 Metz + +Facture FE\FactureNum + +{\addtolength{\leftskip}{10.5cm} + \textbf{\ClientNom} \\ + \ClientAdresse \\ + +} + +\hspace*{10.5cm} +\FactureLieu, le \FactureDate + +~\\~\\ + +\textbf{Objet : \FactureObjet \\} + +\textnormal{\FactureDescr} + +\vspace{10mm} + +\begin{center} + \begin{tabular}{lrrr} + \textbf{Désignation ~~~~~~} & \textbf{Prix unitaire} & \textbf{Quantité} & \textbf{Montant (EUR)} \\ + \hline + {% for product in products %} + {{- product.0 -}} & {{- product.1 -}} \euro{} & {{- product.2 -}} & {{- product.3 -}} \euro{}\\ + {% endfor %} + \hline + \textbf{Total HT} & & & {{- total -}} \euro{} + \end{tabular} +\end{center} + +\vfill + À régler par chèque, espèces ou par virement bancaire : + \begin{center} + \begin{tabular}{|c c c c|} + \hline \textbf{Code banque} & \textbf{Code guichet}& \textbf{Nº de Compte} & \textbf{Clé RIB} \\ + 20041 & 01010 & 1074350Z031 & 48 \\ + \hline \textbf{IBAN Nº} & \multicolumn{3}{|l|}{ FR82 2004 1010 1010 7435 0Z03 148 } \\ + \hline \textbf{BIC} & \multicolumn{3}{|l|}{ PSSTFRPPNCY }\\ + \hline \textbf{Domiciliation} & \multicolumn{3}{|l|}{La Banque Postale - Centre Financier - 54900 Nancy CEDEX 9}\\ + \hline \textbf{Titulaire} & \multicolumn{3}{|l|}{ASSO COOPE TECHNOPOLE METZ}\\ + \hline + \end{tabular} + \end{center} +\end{document} diff --git a/gestion/templates/gestion/kegs_list.html b/gestion/templates/gestion/kegs_list.html index 32b4035..e98bbf1 100644 --- a/gestion/templates/gestion/kegs_list.html +++ b/gestion/templates/gestion/kegs_list.html @@ -28,7 +28,6 @@ Nom Stock en soute - Code barre Capacité Quantité vendue Montant vendu @@ -43,7 +42,6 @@ {{ kegH.keg.name }} {{ kegH.keg.stockHold}} - {{ kegH.keg.barcode }} {{ kegH.keg.capacity }} L {{ kegH.quantitySold }} L {{ kegH.amountSold }} € @@ -77,7 +75,6 @@ Nom Stock en soute - Code barre Capacité Prix du fût Degré @@ -90,7 +87,6 @@ {{ keg.name }} {{ keg.stockHold}} - {{ keg.barcode }} {{ keg.capacity }} L {{ keg.amount }} € {{ keg.pinte.deg }}° diff --git a/gestion/templates/gestion/manage.html b/gestion/templates/gestion/manage.html index e0c4984..d4f49f6 100644 --- a/gestion/templates/gestion/manage.html +++ b/gestion/templates/gestion/manage.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% load static %} -{%block entete%}Gestion de la Coopé™{%endblock%} +{%block entete%}Gestion de la Coopé Technopôle Metz{%endblock%} {% block navbar %} - + diff --git a/templates/nav.html b/templates/nav.html index db0e34d..e31ee29 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -32,9 +32,19 @@ {% if request.user.is_staff %} Stats - + - Comptabilité + Relevé + +{% endif %} +{% if perms.preferences.can_divide %} + + Répartition + +{% endif %} +{% if perms.users.can_generate_invoices %} + + Facture {% endif %} {% if perms.preferences.view_cotisation %} @@ -42,11 +52,19 @@ Cotisations {% endif %} -{% if perms.preferences.view_cotisation %} +{% if perms.preferences.view_paymentmethod %} Moyens de paiement {% endif %} +{% if perms.preferences.view_priceprofile %} + + Profils de prix + + + Calcul de prix + +{% endif %} Deconnexion diff --git a/users/admin.py b/users/admin.py index c222b6e..0ba9f9d 100644 --- a/users/admin.py +++ b/users/admin.py @@ -11,7 +11,7 @@ class CotisationHistoryAdmin(SimpleHistoryAdmin): """ list_display = ('user', 'amount', 'duration', 'paymentDate', 'endDate', 'paymentMethod') ordering = ('user', 'amount', 'duration', 'paymentDate', 'endDate') - search_fields = ('user',) + search_fields = ('user__username', 'user__first_name', 'user__last_name') list_filter = ('paymentMethod', ) class BalanceFilter(admin.SimpleListFilter): @@ -43,16 +43,16 @@ class ProfileAdmin(SimpleHistoryAdmin): """ list_display = ('user', 'credit', 'debit', 'balance', 'school', 'cotisationEnd', 'is_adherent') ordering = ('user', '-credit', '-debit') - search_fields = ('user',) + search_fields = ('user__username', 'user__first_name', 'user__last_name') list_filter = ('school', BalanceFilter) class WhiteListHistoryAdmin(SimpleHistoryAdmin): """ The admin class for :class:`Consumptions `. """ - list_display = ('user', 'paymentDate', 'endDate', 'duration') + list_display = ('user', 'paymentDate', 'endDate', 'duration', 'reason') ordering = ('user', 'duration', 'paymentDate', 'endDate') - search_fields = ('user',) + search_fields = ('user__username', 'user__first_name', 'user__last_name', 'reason') admin.site.register(Permission, SimpleHistoryAdmin) admin.site.register(School, SimpleHistoryAdmin) diff --git a/users/forms.py b/users/forms.py index f1b7e4c..ae4481a 100644 --- a/users/forms.py +++ b/users/forms.py @@ -99,7 +99,7 @@ class addWhiteListHistoryForm(forms.ModelForm): """ class Meta: model = WhiteListHistory - fields = ("duration", ) + fields = ("duration", "reason") class SchoolForm(forms.ModelForm): """ diff --git a/users/migrations/0006_auto_20190611_0105.py b/users/migrations/0006_auto_20190611_0105.py new file mode 100644 index 0000000..caab186 --- /dev/null +++ b/users/migrations/0006_auto_20190611_0105.py @@ -0,0 +1,38 @@ +# Generated by Django 2.1 on 2019-06-10 23:05 + +from django.db import migrations, models + +def update(apps, schema_editor): + db_alias = schema_editor.connection.alias + users = apps.get_model('auth', 'User').objects.using(db_alias).all() + for user in users: + consumptions = apps.get_model('gestion', 'ConsumptionHistory').objects.using(db_alias).filter(customer=user).select_related('product') + alcohol = 0 + for consumption in consumptions: + product = consumption.product + alcohol += consumption.quantity * float(product.deg) * product.volume * 0.79 /10 /1000 + user.profile.alcohol = alcohol + user.profile.save() + +def reverse_update(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0005_auto_20190227_0859'), + ] + + operations = [ + migrations.AddField( + model_name='historicalprofile', + name='alcohol', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5, null=True), + ), + migrations.AddField( + model_name='profile', + name='alcohol', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5, null=True), + ), + migrations.RunPython(update, reverse_update) + ] diff --git a/users/migrations/0007_auto_20190623_0957.py b/users/migrations/0007_auto_20190623_0957.py new file mode 100644 index 0000000..42f5dc8 --- /dev/null +++ b/users/migrations/0007_auto_20190623_0957.py @@ -0,0 +1,42 @@ +# Generated by Django 2.1 on 2019-06-23 07:57 + +from django.db import migrations, models + +def update(apps, schema_editor): + CotisationHistory = apps.get_model('users', 'CotisationHistory') + for cotisation_history in CotisationHistory.objects.all(): + cotisation_history.amount_ptm = cotisation_history.cotisation.amount_ptm + cotisation_history.save() + +def reverse_update(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_auto_20190611_0105'), + ] + + operations = [ + migrations.AddField( + model_name='cotisationhistory', + name='amount_ptm', + field=models.DecimalField(decimal_places=2, max_digits=5, null=True, verbose_name='Montant pour le club Phœnix Technopôle Metz'), + ), + migrations.AddField( + model_name='cotisationhistory', + name='divided', + field=models.BooleanField(default=False, verbose_name='Répartition'), + ), + migrations.AddField( + model_name='historicalcotisationhistory', + name='amount_ptm', + field=models.DecimalField(decimal_places=2, max_digits=5, null=True, verbose_name='Montant pour le club Phœnix Technopôle Metz'), + ), + migrations.AddField( + model_name='historicalcotisationhistory', + name='divided', + field=models.BooleanField(default=False, verbose_name='Répartition'), + ), + migrations.RunPython(update, reverse_update) + ] diff --git a/users/migrations/0008_auto_20190623_1105.py b/users/migrations/0008_auto_20190623_1105.py new file mode 100644 index 0000000..de39a20 --- /dev/null +++ b/users/migrations/0008_auto_20190623_1105.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1 on 2019-06-23 09:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_auto_20190623_0957'), + ] + + operations = [ + migrations.AddField( + model_name='historicalwhitelisthistory', + name='reason', + field=models.CharField(blank=True, max_length=255, verbose_name='Raison'), + ), + migrations.AddField( + model_name='whitelisthistory', + name='reason', + field=models.CharField(blank=True, max_length=255, verbose_name='Raison'), + ), + ] diff --git a/users/migrations/0009_auto_20190623_1437.py b/users/migrations/0009_auto_20190623_1437.py new file mode 100644 index 0000000..1491aa0 --- /dev/null +++ b/users/migrations/0009_auto_20190623_1437.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1 on 2019-06-23 12:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0008_auto_20190623_1105'), + ] + + operations = [ + migrations.AlterModelOptions( + name='profile', + options={'permissions': (('can_generate_invoices', 'Can generate invocies'),), 'verbose_name': 'Profil'}, + ), + ] diff --git a/users/migrations/0010_auto_20190623_1656.py b/users/migrations/0010_auto_20190623_1656.py new file mode 100644 index 0000000..d0392f0 --- /dev/null +++ b/users/migrations/0010_auto_20190623_1656.py @@ -0,0 +1,34 @@ +# Generated by Django 2.1 on 2019-06-23 14:56 + +from django.db import migrations, models + +def update(apps, schema_editor): + User = apps.get_model('auth', 'User') + ConsumptionHistory = apps.get_model('gestion', 'ConsumptionHistory') + for u in User.objects.all(): + chs = ConsumptionHistory.objects.filter(customer=u).filter(paymentMethod__affect_balance=False) + u.profile.direct_debit = sum([x.amount for x in chs]) + u.profile.save() + +def reverse(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0009_auto_20190623_1437'), + ] + + operations = [ + migrations.AddField( + model_name='historicalprofile', + name='direct_debit', + field=models.DecimalField(decimal_places=2, default=0, max_digits=7, verbose_name='Débit (non compte)'), + ), + migrations.AddField( + model_name='profile', + name='direct_debit', + field=models.DecimalField(decimal_places=2, default=0, max_digits=7, verbose_name='Débit (non compte)'), + ), + migrations.RunPython(update, reverse) + ] diff --git a/users/models.py b/users/models.py index beae6fc..c719268 100644 --- a/users/models.py +++ b/users/models.py @@ -61,6 +61,14 @@ class CotisationHistory(models.Model): """ User (:class:`django.contrib.auth.models.User`) who registered the cotisation. """ + divided = models.BooleanField(default=False, verbose_name="Répartition") + """ + True if money of cotisation have been divided between CTM and PTM + """ + amount_ptm = models.DecimalField(max_digits=5, decimal_places=2, null=True, verbose_name="Montant pour le club Phœnix Technopôle Metz") + """ + Amount of money given to the PTM club + """ history = HistoricalRecords() class WhiteListHistory(models.Model): @@ -91,6 +99,10 @@ class WhiteListHistory(models.Model): """ User (:class:`django.contrib.auth.models.User`) who registered the cotisation. """ + reason = models.CharField(max_length=255, verbose_name="Raison", blank=True) + """ + Reason of the whitelist + """ history = HistoricalRecords() class Profile(models.Model): @@ -99,6 +111,7 @@ class Profile(models.Model): """ class Meta: verbose_name = "Profil" + permissions = (('can_generate_invoices', 'Can generate invocies'),) user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name="Utilisateur") """ @@ -110,7 +123,11 @@ class Profile(models.Model): """ debit = models.DecimalField(max_digits=7, decimal_places=2, default=0, verbose_name="Débit") """ - Amount of money, in euros, spent form the account + Amount of money, in euros, spent from the account + """ + direct_debit = models.DecimalField(max_digits=7, decimal_places=2, default=0, verbose_name="Débit (non compte)") + """ + Amount of money, in euro, spent with other mean than the account """ school = models.ForeignKey(School, on_delete=models.PROTECT, blank=True, null=True, verbose_name="École") """ @@ -120,6 +137,10 @@ class Profile(models.Model): """ Date of end of cotisation for the client """ + alcohol = models.DecimalField(max_digits=5, decimal_places=2, default=0, null=True) + """ + Ingerated alcohol + """ history = HistoricalRecords() @property @@ -152,18 +173,6 @@ class Profile(models.Model): """ return Profile.objects.filter(debit__gte=self.debit).count() - @property - def alcohol(self): - """ - Computes ingerated alcohol. - """ - consumptions = ConsumptionHistory.objects.filter(customer=self.user).select_related('product') - alcohol = 0 - for consumption in consumptions: - product = consumption.product - alcohol += consumption.quantity * float(product.deg) * product.volume * 0.79 /10 /1000 - return alcohol - @property def nb_pintes(self): """ diff --git a/users/templates/users/profile.html b/users/templates/users/profile.html index e369a31..0b90ecf 100644 --- a/users/templates/users/profile.html +++ b/users/templates/users/profile.html @@ -38,7 +38,8 @@
  • Solde : {{user.profile.balance}} € Crédit : {{user.profile.credit}} € - Débit : {{user.profile.debit}} € + Débit : {{user.profile.debit}} € + Débit direct : {{user.profile.direct_debit}}
  • Groupe(s) : {{user.groups.all|join:", "}}
  • @@ -263,6 +264,7 @@ Date de l'ajout Date de fin Durée + Raison @@ -271,6 +273,7 @@ {{whitelist.paymentDate}} {{whitelist.endDate}} {{whitelist.duration}} jours + {{ whitelist.reason }} {% endfor %} diff --git a/users/views.py b/users/views.py index 87b849b..c129b7b 100644 --- a/users/views.py +++ b/users/views.py @@ -35,10 +35,7 @@ def loginView(request): if user is not None: login(request, user) messages.success(request, "Vous êtes à présent connecté sous le compte " + str(user)) - if(request.user.has_perm('gestion.can_manage')): - return redirect(reverse('gestion:manage')) - else: - return redirect(reverse('users:profile', kwargs={'pk':request.user.pk})) + return redirect(reverse('home')) else: messages.error(request, "Nom d'utilisateur et/ou mot de passe invalide") return render(request, "form.html", {"form_entete": "Connexion", "form": form, "form_title": "Connexion", "form_button": "Se connecter", "form_button_icon": "sign-in-alt"}) @@ -349,7 +346,7 @@ def gen_user_infos(request, pk): user= get_object_or_404(User, pk=pk) cotisations = CotisationHistory.objects.filter(user=user).order_by('-paymentDate') now = datetime.now() - path = os.path.join(settings.BASE_DIR, "users/templates/users/coope.png") + path = os.path.join(settings.BASE_DIR, "templates/coope.png") return render_to_pdf(request, 'users/bulletin.tex', {"user": user, "now": now, "cotisations": cotisations, "path":path}, filename="bulletin_" + user.first_name + "_" + user.last_name + ".pdf") ########## Groups ########## @@ -586,6 +583,7 @@ def addCotisationHistory(request, pk): cotisation.coopeman = request.user cotisation.amount = cotisation.cotisation.amount cotisation.duration = cotisation.cotisation.duration + cotisation.amount_ptm = cotisation.cotisation.amount_ptm if(user.profile.cotisationEnd and user.profile.cotisationEnd > timezone.now()): cotisation.endDate = user.profile.cotisationEnd + timedelta(days=cotisation.cotisation.duration) else: