From 1ddc0734dc19392e92372b9a7dc236b61e985ef4 Mon Sep 17 00:00:00 2001 From: nanoy Date: Fri, 21 Dec 2018 21:35:09 +0100 Subject: [PATCH 1/9] =?UTF-8?q?Cr=C3=A9ation=20du=20mod=C3=A8le=20pinte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/migrations/0002_pinte.py | 25 +++++++++++++++++++++++++ gestion/models.py | 8 ++++++++ 2 files changed, 33 insertions(+) create mode 100644 gestion/migrations/0002_pinte.py diff --git a/gestion/migrations/0002_pinte.py b/gestion/migrations/0002_pinte.py new file mode 100644 index 0000000..93f21fb --- /dev/null +++ b/gestion/migrations/0002_pinte.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1 on 2018-12-21 20:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gestion', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Pinte', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('last_update_date', models.DateTimeField(auto_now=True)), + ('current_owner', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pinte_owned_currently', to=settings.AUTH_USER_MODEL)), + ('previous_owner', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pinte_owned_previously', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 33828d6..f69c16e 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -225,3 +225,11 @@ class Consumption(models.Model): def __str__(self): return "Consommation de " + str(self.customer) + " concernant le produit " + str(self.product) + +class Pinte(models.Model): + """ + Stores a physical pinte + """ + current_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_currently") + previous_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_previously") + last_update_date = models.DateTimeField(auto_now=True) From 00b0e8e0a01e894daf4d1e446b31ddcd407437d5 Mon Sep 17 00:00:00 2001 From: nanoy Date: Fri, 21 Dec 2018 21:54:39 +0100 Subject: [PATCH 2/9] =?UTF-8?q?Pr=C3=A9f=C3=A9rences=20pour=20l'utilisatio?= =?UTF-8?q?n=20du=20suivi=20de=20pinte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/migrations/0003_historicalpinte.py | 37 +++++++++++++++++++ gestion/models.py | 1 + .../migrations/0002_auto_20181221_2151.py | 23 ++++++++++++ preferences/models.py | 1 + .../preferences/general_preferences.html | 20 +++++++++- 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 gestion/migrations/0003_historicalpinte.py create mode 100644 preferences/migrations/0002_auto_20181221_2151.py diff --git a/gestion/migrations/0003_historicalpinte.py b/gestion/migrations/0003_historicalpinte.py new file mode 100644 index 0000000..243fc58 --- /dev/null +++ b/gestion/migrations/0003_historicalpinte.py @@ -0,0 +1,37 @@ +# Generated by Django 2.1 on 2018-12-21 20:51 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gestion', '0002_pinte'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalPinte', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('last_update_date', models.DateTimeField(blank=True, editable=False)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_date', models.DateTimeField()), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('current_owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('previous_owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical pinte', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/gestion/models.py b/gestion/models.py index f69c16e..64a898e 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -233,3 +233,4 @@ class Pinte(models.Model): current_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_currently") previous_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_previously") last_update_date = models.DateTimeField(auto_now=True) + history = HistoricalRecords() diff --git a/preferences/migrations/0002_auto_20181221_2151.py b/preferences/migrations/0002_auto_20181221_2151.py new file mode 100644 index 0000000..f02f5f4 --- /dev/null +++ b/preferences/migrations/0002_auto_20181221_2151.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1 on 2018-12-21 20:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='generalpreferences', + name='use_pinte_monitoring', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='historicalgeneralpreferences', + name='use_pinte_monitoring', + field=models.BooleanField(default=False), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 8f41f74..3bff4f1 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -30,6 +30,7 @@ class GeneralPreferences(models.Model): secretary = models.CharField(max_length=255, blank=True) brewer = models.CharField(max_length=255, blank=True) grocer = models.CharField(max_length=255, blank=True) + use_pinte_monitoring = models.BooleanField(default=False) history = HistoricalRecords() class Cotisation(models.Model): diff --git a/preferences/templates/preferences/general_preferences.html b/preferences/templates/preferences/general_preferences.html index 1addb98..06ac677 100644 --- a/preferences/templates/preferences/general_preferences.html +++ b/preferences/templates/preferences/general_preferences.html @@ -1,10 +1,11 @@ {% extends 'base.html' %} {% block entete %}Administration{% endblock %} -{% block nav %} +{% block navbar %} {% endblock %} @@ -75,6 +76,21 @@ {{form.brewer}} + + + +
+
+
+
+

Autre

+
+
+
+ {{form.use_pinte_monitoring}} + +
+
From 3ea5f0322ac160b44222d8b701a74616798dc0e0 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 11:53:33 +0100 Subject: [PATCH 3/9] =?UTF-8?q?Demande=20du=20num=C3=A9ro=20de=20pinte=20l?= =?UTF-8?q?ors=20de=20la=20commande=20et=20allocation=20de=20la=20pinte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/views.py | 35 ++++++++++++++++++++++++++---- staticfiles/manage.js | 50 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/gestion/views.py b/gestion/views.py index 0111557..c49854e 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -14,7 +14,7 @@ from dal import autocomplete from decimal import * from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm -from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory +from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte from preferences.models import PaymentMethod @active_required @@ -107,6 +107,7 @@ def order(request): amount = Decimal(request.POST['amount']) order = json.loads(request.POST["order"]) menus = json.loads(request.POST["menus"]) + listPintes = json.loads(request.POST["listPintes"]) if (not order) and (not menus): return HttpResponse("Pas de commande") adherentRequired = False @@ -124,6 +125,8 @@ def order(request): else: user.profile.debit += amount user.save() + for pinte in listPintes: + allocate(pinte, user) for o in order: product = get_object_or_404(Product, pk=o["pk"]) quantity = int(o["quantity"]) @@ -418,7 +421,11 @@ def getProduct(request, barcode): The requested barcode """ product = Product.objects.get(barcode=barcode) - data = json.dumps({"pk": product.pk, "barcode" : product.barcode, "name": product.name, "amount" : product.amount}) + if product.category == Product.P_PRESSION: + nb_pintes = 1 + else: + nb_pintes = 0 + data = json.dumps({"pk": product.pk, "barcode" : product.barcode, "name": product.name, "amount": product.amount, "needQuantityButton": product.needQuantityButton, "nb_pintes": nb_pintes}) return HttpResponse(data, content_type='application/json') @active_required @@ -845,7 +852,11 @@ def get_menu(request, barcode): The requested barcode """ menu = get_object_or_404(Menu, barcode=barcode) - data = json.dumps({"pk": menu.pk, "barcode" : menu.barcode, "name": menu.name, "amount" : menu.amount}) + nb_pintes = 0 + for article in menu.articles: + if article.category == Product.P_PRESSION: + nb_pintes +=1 + data = json.dumps({"pk": menu.pk, "barcode" : menu.barcode, "name": menu.name, "amount" : menu.amount, needQuantityButton: False, "nb_pintes": nb_pintes}) return HttpResponse(data, content_type='application/json') class MenusAutocomplete(autocomplete.Select2QuerySetView): @@ -885,4 +896,20 @@ def ranking(request): alcohol = customer.profile.alcohol list.append([customer, alcohol]) bestDrinkers = sorted(list, key=lambda x: x[1], reverse=True)[:25] - return render(request, "gestion/ranking.html", {"bestBuyers": bestBuyers, "bestDrinkers": bestDrinkers}) \ No newline at end of file + return render(request, "gestion/ranking.html", {"bestBuyers": bestBuyers, "bestDrinkers": bestDrinkers}) + +########## Pinte monitoring ########## + +def allocate(pinte_pk, user): + """ + Allocate a pinte to a user or release the pinte if user is None + """ + try: + pinte = Pinte.objects.get(pk=pinte_pk) + if pinte.current_owner is not None: + pinte.previous_owner = pinte.current_owner + pinte.current_owner = user + pinte.save() + return True + except Pinte.DoesNotExist: + return False \ No newline at end of file diff --git a/staticfiles/manage.js b/staticfiles/manage.js index 39e174a..7af6bc6 100644 --- a/staticfiles/manage.js +++ b/staticfiles/manage.js @@ -5,20 +5,29 @@ paymentMethod = null balance = 0 username = "" id = 0 +listPintes = [] +nbPintes = 0; +use_pinte_monitoring = false; + +function get_config(){ + use_pinte_monitoring = true; +} function get_product(barcode){ res = $.get("getProduct/" + barcode, function(data){ - add_product(data.pk, data.barcode, data.name, data.amount); + nbPintes += data.nb_pintes; + add_product(data.pk, data.barcode, data.name, data.amount, data.needQuantityButton); }); } function get_menu(barcode){ res = $.get("getMenu/" + barcode, function(data){ - add_menu(data.pk, data.barcode, data.name, data.amount); + nbPintes += data.nb_pintes; + add_menu(data.pk, data.barcode, data.name, data.amount, data.needQuantityButton); }); } -function add_product(pk, barcode, name, amount){ +function add_product(pk, barcode, name, amount, needQuantityButton){ exist = false index = -1 for(k=0;k < products.length; k++){ @@ -27,10 +36,18 @@ function add_product(pk, barcode, name, amount){ index = k } } - if(exist){ - products[index].quantity += 1; + if(needQuantityButton){ + quantity = parseInt(window.prompt("Quantité ?","")); }else{ - products.push({"pk": pk, "barcode": barcode, "name": name, "amount": amount, "quantity": 1}); + quantity = 1; + } + if(quantity == null || !Number.isInteger(quantity)){ + quantity = 1; + } + if(exist){ + products[index].quantity += quantity; + }else{ + products.push({"pk": pk, "barcode": barcode, "name": name, "amount": amount, "quantity": quantity}); } generate_html() } @@ -53,7 +70,7 @@ function add_menu(pk, barcode, name, amount){ } function generate_html(){ - html ="" + html = ""; for(k=0;k' + product.name + '' + String(product.amount) + '' + String(Number((product.quantity * product.amount).toFixed(2))) + ''; @@ -94,6 +111,7 @@ function updateMenuInput(a){ } $(document).ready(function(){ + get_config(); $(".product").click(function(){ product = get_product($(this).attr('target')); }); @@ -113,7 +131,23 @@ $(document).ready(function(){ }); }); $(".pay_button").click(function(){ - $.post("order", {"user":id, "paymentMethod": $(this).attr('data-payment'), "order_length": products.length + menus.length, "order": JSON.stringify(products), "amount": total, "menus": JSON.stringify(menus)}, function(data){ + message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" + while(nbPintes > 0){ + id_pinte = window.prompt(message,""); + if(id_pinte == null){ + return; + }else{ + id_pinte = parseInt(id_pinte); + if(!Number.isInteger(id_pinte) || id_pinte < 0){ + message = "Numéro incorrect. Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?"; + }else{ + listPintes.push(id_pinte) + nbPintes -= 1; + message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" + } + } + } + $.post("order", {"user":id, "paymentMethod": $(this).attr('data-payment'), "order_length": products.length + menus.length, "order": JSON.stringify(products), "amount": total, "menus": JSON.stringify(menus), "listPintes": JSON.stringify(listPintes)}, function(data){ alert(data); location.reload(); }).fail(function(data){ From 6b3136dece1e0953ec76252d940186fb531f758e Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 12:06:16 +0100 Subject: [PATCH 4/9] =?UTF-8?q?Prise=20en=20compte=20des=20pr=C3=A9f=C3=A9?= =?UTF-8?q?rences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- preferences/urls.py | 1 + preferences/views.py | 14 ++++++++++++++ staticfiles/manage.js | 31 ++++++++++++++++++------------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/preferences/urls.py b/preferences/urls.py index 7981698..dd7d3b8 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -14,4 +14,5 @@ urlpatterns = [ path('editPaymentMethod/', views.editPaymentMethod, name="editPaymentMethod"), path('deletePaymentMethod/', views.deletePaymentMethod, name="deletePaymentMethod"), path('inactive', views.inactive, name="inactive"), + path('getConfig', views.get_config, name="getConfig"), ] diff --git a/preferences/views.py b/preferences/views.py index 08c8976..3f9ec6d 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -1,7 +1,11 @@ +import json + from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.urls import reverse from django.contrib.auth.decorators import login_required, permission_required +from django.http import HttpResponse +from django.forms.models import model_to_dict from coopeV3.acl import active_required @@ -238,4 +242,14 @@ def inactive(request): """ gp, _ = GeneralPreferences.objects.get_or_create(pk=1) return render(request, 'preferences/inactive.html', {"message": gp.active_message}) + +########## Config ########## + +def get_config(request): + """ + Load the config and return it in a json format + """ + gp,_ = GeneralPreferences.objects.get_or_create(pk=1) + data = json.dumps(model_to_dict(gp)) + return HttpResponse(data, content_type='application/json') \ No newline at end of file diff --git a/staticfiles/manage.js b/staticfiles/manage.js index 7af6bc6..ad2691b 100644 --- a/staticfiles/manage.js +++ b/staticfiles/manage.js @@ -10,7 +10,10 @@ nbPintes = 0; use_pinte_monitoring = false; function get_config(){ - use_pinte_monitoring = true; + res = $.get("../preferences/getConfig", function(data){ + console.log(data.use_pinte_monitoring) + use_pinte_monitoring = data.use_pinte_monitoring; + }); } function get_product(barcode){ @@ -131,19 +134,21 @@ $(document).ready(function(){ }); }); $(".pay_button").click(function(){ - message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" - while(nbPintes > 0){ - id_pinte = window.prompt(message,""); - if(id_pinte == null){ - return; - }else{ - id_pinte = parseInt(id_pinte); - if(!Number.isInteger(id_pinte) || id_pinte < 0){ - message = "Numéro incorrect. Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?"; + if(use_pinte_monitoring){ + message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" + while(nbPintes > 0){ + id_pinte = window.prompt(message,""); + if(id_pinte == null){ + return; }else{ - listPintes.push(id_pinte) - nbPintes -= 1; - message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" + id_pinte = parseInt(id_pinte); + if(!Number.isInteger(id_pinte) || id_pinte < 0){ + message = "Numéro incorrect. Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?"; + }else{ + listPintes.push(id_pinte) + nbPintes -= 1; + message = "Il reste " + nbPintes.toString() + " pintes à renseigner. Numéro de la pinte ?" + } } } } From 1a1b5cf60b4bc0aaaa231a41daf32bab2bd5c6c0 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 12:54:37 +0100 Subject: [PATCH 5/9] =?UTF-8?q?Cr=C3=A9ation=20de=20pintes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/forms.py | 7 +++++- gestion/templates/gestion/products_index.html | 18 +++++++++++++ gestion/urls.py | 1 + gestion/views.py | 25 +++++++++++++++++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/gestion/forms.py b/gestion/forms.py index 95894b7..0c560a8 100644 --- a/gestion/forms.py +++ b/gestion/forms.py @@ -60,4 +60,9 @@ class SelectPositiveKegForm(forms.Form): keg = forms.ModelChoiceField(queryset=Keg.objects.filter(stockHold__gt = 0), required=True, label="Fût", widget=autocomplete.ModelSelect2(url='gestion:kegs-positive-autocomplete')) class SelectActiveKegForm(forms.Form): - keg = forms.ModelChoiceField(queryset=Keg.objects.filter(is_active = True), required=True, label="Fût", widget=autocomplete.ModelSelect2(url='gestion:kegs-active-autocomplete')) \ No newline at end of file + keg = forms.ModelChoiceField(queryset=Keg.objects.filter(is_active = True), required=True, label="Fût", widget=autocomplete.ModelSelect2(url='gestion:kegs-active-autocomplete')) + +class CreatePinteForm(forms.Form): + ids = forms.CharField(widget=forms.Textarea, label="Numéros", help_text="Numéros séparés par un espace. Laissez vide pour utiliser le range.", required=False) + begin = forms.IntegerField(label="Début", help_text="Début du range", required=False) + end = forms.IntegerField(label="Fin", help_text="Fin du range", required=False) \ No newline at end of file diff --git a/gestion/templates/gestion/products_index.html b/gestion/templates/gestion/products_index.html index 2a3ade6..21b8393 100644 --- a/gestion/templates/gestion/products_index.html +++ b/gestion/templates/gestion/products_index.html @@ -5,6 +5,7 @@
  • Produits
  • Futs
  • Menus
  • +
  • Pintes
  • {% endblock %} {% block content %} @@ -58,4 +59,21 @@ {% endif %}
    +
    +
    +

    Pintes

    +
    + Actions possibles : + +
    {% endblock %} diff --git a/gestion/urls.py b/gestion/urls.py index c05a330..3fb9e41 100644 --- a/gestion/urls.py +++ b/gestion/urls.py @@ -33,6 +33,7 @@ urlpatterns = [ path('cancelConsumption/', views.cancel_consumption, name="cancelConsumption"), path('cancelMenu/', views.cancel_menu, name="cancelMenu"), path('productProfile/', views.productProfile, name="productProfile"), + path('createPintes', views.create_pintes, name="createPintes"), path('products-autocomplete', views.ProductsAutocomplete.as_view(), name="products-autocomplete"), path('kegs-positive-autocomplete', views.KegPositiveAutocomplete.as_view(), name="kegs-positive-autocomplete"), path('kegs-active-autocomplete', views.KegActiveAutocomplete.as_view(), name="kegs-active-autocomplete"), diff --git a/gestion/views.py b/gestion/views.py index c49854e..f87f020 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -13,7 +13,7 @@ import simplejson as json from dal import autocomplete from decimal import * -from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm +from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, CreatePinteForm from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte from preferences.models import PaymentMethod @@ -912,4 +912,25 @@ def allocate(pinte_pk, user): pinte.save() return True except Pinte.DoesNotExist: - return False \ No newline at end of file + return False + +@active_required +@login_required +@permission_required('gestion.add_pinte') +def create_pintes(request): + form = CreatePinteForm(request.POST or None) + if form.is_valid(): + ids = form.cleaned_data['ids'] + if ids != "": + ids = ids.split(" ") + else: + ids = range(form.cleaned_data['begin'], form.cleaned_data['end'] + 1) + i = 0 + for id in ids: + if not Pinte.objects.filter(pk=id).exists(): + new_pinte = Pinte(pk=int(id)) + new_pinte.save() + i += 1 + messages.success(request, str(i) + " pinte(s) a(ont) été ajoutée(s)") + return redirect(reverse('gestion:productsIndex')) + return render(request, "form.html", {"form": form, "form_title": "Ajouter des pintes", "form_button": "Ajouter"}) \ No newline at end of file From bba6e3300d01e842b681387ec0097fb38539f8f9 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 13:05:41 +0100 Subject: [PATCH 6/9] =?UTF-8?q?Lib=C3=A9rer=20des=20pintes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/forms.py | 2 +- gestion/templates/gestion/products_index.html | 2 +- gestion/urls.py | 1 + gestion/views.py | 25 ++++++++++++++++--- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/gestion/forms.py b/gestion/forms.py index 0c560a8..325f1b2 100644 --- a/gestion/forms.py +++ b/gestion/forms.py @@ -62,7 +62,7 @@ class SelectPositiveKegForm(forms.Form): class SelectActiveKegForm(forms.Form): keg = forms.ModelChoiceField(queryset=Keg.objects.filter(is_active = True), required=True, label="Fût", widget=autocomplete.ModelSelect2(url='gestion:kegs-active-autocomplete')) -class CreatePinteForm(forms.Form): +class PinteForm(forms.Form): ids = forms.CharField(widget=forms.Textarea, label="Numéros", help_text="Numéros séparés par un espace. Laissez vide pour utiliser le range.", required=False) begin = forms.IntegerField(label="Début", help_text="Début du range", required=False) end = forms.IntegerField(label="Fin", help_text="Fin du range", required=False) \ No newline at end of file diff --git a/gestion/templates/gestion/products_index.html b/gestion/templates/gestion/products_index.html index 21b8393..5b5543a 100644 --- a/gestion/templates/gestion/products_index.html +++ b/gestion/templates/gestion/products_index.html @@ -69,7 +69,7 @@
  • Créer une ou plusieurs pintes
  • {% endif %} {% if perms.gestion.change_pinte %} -
  • Libérer des pintes
  • +
  • Libérer des pintes
  • {% endif %} {% if perms.gestion.view_pinte %}
  • Lister les pintes
  • diff --git a/gestion/urls.py b/gestion/urls.py index 3fb9e41..e2bc803 100644 --- a/gestion/urls.py +++ b/gestion/urls.py @@ -34,6 +34,7 @@ urlpatterns = [ path('cancelMenu/', views.cancel_menu, name="cancelMenu"), path('productProfile/', views.productProfile, name="productProfile"), path('createPintes', views.create_pintes, name="createPintes"), + path('releasePintes', views.release_pintes, name="releasePintes"), path('products-autocomplete', views.ProductsAutocomplete.as_view(), name="products-autocomplete"), path('kegs-positive-autocomplete', views.KegPositiveAutocomplete.as_view(), name="kegs-positive-autocomplete"), path('kegs-active-autocomplete', views.KegActiveAutocomplete.as_view(), name="kegs-active-autocomplete"), diff --git a/gestion/views.py b/gestion/views.py index f87f020..0e70809 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -13,7 +13,7 @@ import simplejson as json from dal import autocomplete from decimal import * -from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, CreatePinteForm +from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, PinteForm from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte from preferences.models import PaymentMethod @@ -918,7 +918,7 @@ def allocate(pinte_pk, user): @login_required @permission_required('gestion.add_pinte') def create_pintes(request): - form = CreatePinteForm(request.POST or None) + form = PinteForm(request.POST or None) if form.is_valid(): ids = form.cleaned_data['ids'] if ids != "": @@ -933,4 +933,23 @@ def create_pintes(request): i += 1 messages.success(request, str(i) + " pinte(s) a(ont) été ajoutée(s)") return redirect(reverse('gestion:productsIndex')) - return render(request, "form.html", {"form": form, "form_title": "Ajouter des pintes", "form_button": "Ajouter"}) \ No newline at end of file + return render(request, "form.html", {"form": form, "form_title": "Ajouter des pintes", "form_button": "Ajouter"}) + +@active_required +@login_required +@permission_required('gestion.change_pinte') +def release_pintes(request): + form = PinteForm(request.POST or None) + if form.is_valid(): + ids = form.cleaned_data['ids'] + if ids != "": + ids = ids.split(" ") + else: + ids = range(form.cleaned_data['begin'], form.cleaned_data['end'] + 1) + i = 0 + for id in ids: + if allocate(id, None): + i += 1 + messages.success(request, str(i) + " pinte(s) a(ont) été libérée(s)") + return redirect(reverse('gestion:productsIndex')) + return render(request, "form.html", {"form": form, "form_title": "Libérer des pintes", "form_button": "Libérer"}) \ No newline at end of file From 8d26381f6de80884598e45ca653f5255da0236c7 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 14:36:29 +0100 Subject: [PATCH 7/9] Liste des pintes --- gestion/templates/gestion/products_index.html | 4 +-- gestion/urls.py | 4 ++- gestion/views.py | 25 +++++++++++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/gestion/templates/gestion/products_index.html b/gestion/templates/gestion/products_index.html index 5b5543a..7df35e8 100644 --- a/gestion/templates/gestion/products_index.html +++ b/gestion/templates/gestion/products_index.html @@ -66,13 +66,13 @@ Actions possibles : diff --git a/gestion/urls.py b/gestion/urls.py index e2bc803..ffce4b8 100644 --- a/gestion/urls.py +++ b/gestion/urls.py @@ -33,8 +33,10 @@ urlpatterns = [ path('cancelConsumption/', views.cancel_consumption, name="cancelConsumption"), path('cancelMenu/', views.cancel_menu, name="cancelMenu"), path('productProfile/', views.productProfile, name="productProfile"), - path('createPintes', views.create_pintes, name="createPintes"), + path('addPintes', views.add_pintes, name="addPintes"), path('releasePintes', views.release_pintes, name="releasePintes"), + path('pintesList', views.pintes_list, name="pintesList"), + path('release/', views.release, name="release"), path('products-autocomplete', views.ProductsAutocomplete.as_view(), name="products-autocomplete"), path('kegs-positive-autocomplete', views.KegPositiveAutocomplete.as_view(), name="kegs-positive-autocomplete"), path('kegs-active-autocomplete', views.KegActiveAutocomplete.as_view(), name="kegs-active-autocomplete"), diff --git a/gestion/views.py b/gestion/views.py index 0e70809..1767943 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -914,10 +914,23 @@ def allocate(pinte_pk, user): except Pinte.DoesNotExist: return False +@active_required +@login_required +@permission_required('gestion.change_pinte') +def release(request, pinte_pk): + """ + View to release a pinte + """ + if allocate(pinte_pk, None): + messages.success(request, "La pinte a bien été libérée") + else: + messages.error(request, "Impossible de libérer la pinte") + return redirect(reverse('gestion:pintesList')) + @active_required @login_required @permission_required('gestion.add_pinte') -def create_pintes(request): +def add_pintes(request): form = PinteForm(request.POST or None) if form.is_valid(): ids = form.cleaned_data['ids'] @@ -952,4 +965,12 @@ def release_pintes(request): i += 1 messages.success(request, str(i) + " pinte(s) a(ont) été libérée(s)") return redirect(reverse('gestion:productsIndex')) - return render(request, "form.html", {"form": form, "form_title": "Libérer des pintes", "form_button": "Libérer"}) \ No newline at end of file + return render(request, "form.html", {"form": form, "form_title": "Libérer des pintes", "form_button": "Libérer"}) + +@active_required +@login_required +@permission_required('gestion.view_pinte') +def pintes_list(request): + free_pintes = Pinte.objects.filter(current_owner=None) + taken_pintes = Pinte.objects.exclude(current_owner=None) + return render(request, "gestion/pintes_list.html", {"free_pintes": free_pintes, "taken_pintes": taken_pintes}) \ No newline at end of file From e28f564afac25dfd49bc5f428cd07158ee7e1c62 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 14:36:36 +0100 Subject: [PATCH 8/9] Liste des pintes --- gestion/templates/gestion/pintes_list.html | 75 ++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 gestion/templates/gestion/pintes_list.html diff --git a/gestion/templates/gestion/pintes_list.html b/gestion/templates/gestion/pintes_list.html new file mode 100644 index 0000000..88c0b95 --- /dev/null +++ b/gestion/templates/gestion/pintes_list.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} +{% block entete %}Gestion des produits{% endblock %} +{% block navbar%} + +{% endblock %} +{% block content %} +
    +
    +

    Général

    +
    + {% if perms.gestion.add_pinte %} + Créer une ou plusieurs pintes

    + {% endif %} + Il a y actuellement {{ taken_pintes.count|add:free_pintes.count }} pintes, parmis lesquelles {{ free_pintes.count }} sont rendues et {{ taken_pintes.count }} ne sont pas rendues. +
    +
    +
    +

    Liste des pintes non rendues

    +
    +
    + + + + + + + + + + + + {% for pinte in taken_pintes %} + + + + + + + + {% endfor %} + +
    NuméroPossesseur actuelPossesseur précédentDate du dernier changementAdministrer
    {{ pinte.pk }}{{ pinte.current_owner }}{{ pinte.previous_owner }}{{ pinte.last_update_date }}{% if perms.gestion.change_pinte %} Libérer{% endif %}
    +
    +
    +
    +
    +

    Liste des pintes rendues

    +
    +
    + + + + + + + + + + {% for pinte in free_pintes %} + + + + + + {% endfor %} + +
    NuméroPossesseur précédentDate du dernier changement
    {{ pinte.pk }}{{ pinte.previous_owner }}{{ pinte.last_update_date }}
    +
    +
    +{% endblock %} From e5456b4d210693f3350afe0e524c021990a43923 Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 23 Dec 2018 18:52:18 +0100 Subject: [PATCH 9/9] =?UTF-8?q?Empecher=20l'utilisateur=20de=20consommer?= =?UTF-8?q?=20s'il=20a=20gard=C3=A9=20trop=20de=20pintes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/migrations/0004_auto_20181223_1830.py | 25 +++++++++++++++++++ gestion/models.py | 4 +-- gestion/views.py | 13 +++++++--- .../migrations/0003_auto_20181223_1440.py | 23 +++++++++++++++++ preferences/models.py | 1 + .../preferences/general_preferences.html | 12 ++++++--- users/models.py | 14 +++++++++-- 7 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 gestion/migrations/0004_auto_20181223_1830.py create mode 100644 preferences/migrations/0003_auto_20181223_1440.py diff --git a/gestion/migrations/0004_auto_20181223_1830.py b/gestion/migrations/0004_auto_20181223_1830.py new file mode 100644 index 0000000..de74c17 --- /dev/null +++ b/gestion/migrations/0004_auto_20181223_1830.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1 on 2018-12-23 17:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0003_historicalpinte'), + ] + + operations = [ + migrations.AlterField( + model_name='pinte', + name='current_owner', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pintes_owned_currently', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='pinte', + name='previous_owner', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pintes_owned_previously', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 64a898e..ee03f91 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -230,7 +230,7 @@ class Pinte(models.Model): """ Stores a physical pinte """ - current_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_currently") - previous_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pinte_owned_previously") + current_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pintes_owned_currently") + previous_owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, default=None, related_name="pintes_owned_previously") last_update_date = models.DateTimeField(auto_now=True) history = HistoricalRecords() diff --git a/gestion/views.py b/gestion/views.py index 1767943..f19d30a 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -15,7 +15,7 @@ from decimal import * from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, PinteForm from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte -from preferences.models import PaymentMethod +from preferences.models import PaymentMethod, GeneralPreferences @active_required @login_required @@ -108,6 +108,7 @@ def order(request): order = json.loads(request.POST["order"]) menus = json.loads(request.POST["menus"]) listPintes = json.loads(request.POST["listPintes"]) + gp,_ = GeneralPreferences.objects.get_or_create(pk=1) if (not order) and (not menus): return HttpResponse("Pas de commande") adherentRequired = False @@ -119,14 +120,20 @@ def order(request): adherentRequired = adherentRequired or menu.adherent_required if(adherentRequired and not user.profile.is_adherent): return HttpResponse("N'est pas adhérent et devrait l'être") + # Partie un peu complexe : je libère toutes les pintes de la commande, puis je test + # s'il a trop de pintes non rendues, puis je réalloue les pintes + for pinte in listPintes: + allocate(pinte, None) + if(gp.lost_pintes_allowed and user.profile.nb_pintes >= gp.lost_pintes_allowed): + return HttpResponse("Impossible de réaliser la commande : l'utilisateur a perdu trop de pintes.") + for pinte in listPintes: + allocate(pinte, user) if(paymentMethod.affect_balance): if(user.profile.balance < amount): return HttpResponse("Solde inférieur au prix de la commande") else: user.profile.debit += amount user.save() - for pinte in listPintes: - allocate(pinte, user) for o in order: product = get_object_or_404(Product, pk=o["pk"]) quantity = int(o["quantity"]) diff --git a/preferences/migrations/0003_auto_20181223_1440.py b/preferences/migrations/0003_auto_20181223_1440.py new file mode 100644 index 0000000..b1a489b --- /dev/null +++ b/preferences/migrations/0003_auto_20181223_1440.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1 on 2018-12-23 13:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0002_auto_20181221_2151'), + ] + + operations = [ + migrations.AddField( + model_name='generalpreferences', + name='lost_pintes_allowed', + field=models.PositiveIntegerField(default=0), + ), + migrations.AddField( + model_name='historicalgeneralpreferences', + name='lost_pintes_allowed', + field=models.PositiveIntegerField(default=0), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 3bff4f1..0ac2d69 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -31,6 +31,7 @@ class GeneralPreferences(models.Model): brewer = models.CharField(max_length=255, blank=True) grocer = models.CharField(max_length=255, blank=True) use_pinte_monitoring = models.BooleanField(default=False) + lost_pintes_allowed = models.PositiveIntegerField(default=0) history = HistoricalRecords() class Cotisation(models.Model): diff --git a/preferences/templates/preferences/general_preferences.html b/preferences/templates/preferences/general_preferences.html index 06ac677..08596d6 100644 --- a/preferences/templates/preferences/general_preferences.html +++ b/preferences/templates/preferences/general_preferences.html @@ -5,7 +5,7 @@
  • Message global
  • Site actif
  • Bureau
  • -
  • Autre
  • +
  • Suivi de pintes
  • {% endblock %} @@ -83,12 +83,18 @@
    -

    Autre

    +

    Suivi de pintes

    {{form.use_pinte_monitoring}} - + +
    +
    +
    +
    + {{form.lost_pintes_allowed}} +
    diff --git a/users/models.py b/users/models.py index bda3ba2..c3b0df9 100644 --- a/users/models.py +++ b/users/models.py @@ -119,6 +119,13 @@ class Profile(models.Model): alcohol += consumption.quantity * float(product.deg) * product.volume * 0.79 /10 /1000 return alcohol + @property + def nb_pintes(self): + """ + Return the number of pintes currently owned + """ + return self.user.pintes_owned_currently.count() + def __str__(self): return str(self.user) @@ -128,9 +135,12 @@ class Profile(models.Model): tente de retourner l'attribut de l'user associé à l'instance """ try: - r = super().__getattr__(name) + r = self.__getattribute__(name) except AttributeError: - r = getattr(self.user, name) + try: + r = super().__getattr__(name) + except AttributeError: + r = getattr(self.user, name) return r