diff --git a/gestion/admin.py b/gestion/admin.py index 65d67ea..1fa3040 100644 --- a/gestion/admin.py +++ b/gestion/admin.py @@ -1,9 +1,11 @@ from django.contrib import admin -from .models import Reload, Refund, Product, Keg, ConsumptionHistory +from .models import Reload, Refund, Product, Keg, ConsumptionHistory, KegHistory, Consumption admin.site.register(Reload) admin.site.register(Refund) admin.site.register(Product) admin.site.register(Keg) -admin.site.register(ConsumptionHistory) \ No newline at end of file +admin.site.register(ConsumptionHistory) +admin.site.register(KegHistory) +admin.site.register(Consumption) \ No newline at end of file diff --git a/gestion/forms.py b/gestion/forms.py index 8d3250c..89a694f 100644 --- a/gestion/forms.py +++ b/gestion/forms.py @@ -64,4 +64,10 @@ class SearchMenuForm(forms.Form): menu = forms.ModelChoiceField(queryset=Menu.objects.all(), required=True, label="Menu", widget=autocomplete.ModelSelect2(url='gestion:menus-autocomplete', attrs={'data-minimum-input-length':2})) class GestionForm(forms.Form): - client = forms.ModelChoiceField(queryset=User.objects.filter(is_active=True), required=True, label="Client", widget=autocomplete.ModelSelect2(url='users:active-users-autocomplete', attrs={'data-minimum-input-length':2})) \ No newline at end of file + client = forms.ModelChoiceField(queryset=User.objects.filter(is_active=True), required=True, label="Client", widget=autocomplete.ModelSelect2(url='users:active-users-autocomplete', attrs={'data-minimum-input-length':2})) + +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 diff --git a/gestion/migrations/0002_auto_20181123_0229.py b/gestion/migrations/0002_auto_20181123_0229.py new file mode 100644 index 0000000..aaaebaa --- /dev/null +++ b/gestion/migrations/0002_auto_20181123_0229.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1 on 2018-11-23 01:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='keghistory', + old_name='Keg', + new_name='keg', + ), + migrations.AlterField( + model_name='keghistory', + name='amountSold', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + migrations.AlterField( + model_name='keghistory', + name='closingDate', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='keghistory', + name='quantitySold', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + ] diff --git a/gestion/migrations/0003_auto_20181123_0330.py b/gestion/migrations/0003_auto_20181123_0330.py new file mode 100644 index 0000000..b0e98c5 --- /dev/null +++ b/gestion/migrations/0003_auto_20181123_0330.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1 on 2018-11-23 02:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0002_auto_20181123_0229'), + ] + + operations = [ + migrations.AlterModelOptions( + name='keg', + options={'permissions': (('open_keg', 'Peut percuter les fûts'), ('close_keg', 'Peut fermer les fûts'))}, + ), + migrations.AddField( + model_name='product', + name='adherentRequired', + field=models.BooleanField(default=True), + ), + ] diff --git a/gestion/migrations/0004_consumption.py b/gestion/migrations/0004_consumption.py new file mode 100644 index 0000000..cbb1f18 --- /dev/null +++ b/gestion/migrations/0004_consumption.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1 on 2018-11-23 13:03 + +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', '0003_auto_20181123_0330'), + ] + + operations = [ + migrations.CreateModel( + name='Consumption', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=0)), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='consumption_global_taken', to=settings.AUTH_USER_MODEL)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Product')), + ], + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 94da081..0e019bf 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -30,6 +30,7 @@ class Product(models.Model): is_active = models.BooleanField(default=True, verbose_name="Actif") volume = models.IntegerField(default=0) deg = models.DecimalField(default=0,max_digits=5, decimal_places=2, verbose_name="Degré") + adherentRequired = models.BooleanField(default=True) def __str__(self): return self.name @@ -61,6 +62,12 @@ def isGalopin(id): ) class Keg(models.Model): + class Meta: + permissions = ( + ("open_keg", "Peut percuter les fûts"), + ("close_keg", "Peut fermer les fûts") + ) + name = models.CharField(max_length=20, unique=True, verbose_name="Nom") stockHold = models.IntegerField(default=0, verbose_name="Stock en soute") barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre") @@ -75,13 +82,21 @@ class Keg(models.Model): return self.name class KegHistory(models.Model): - Keg = models.ForeignKey(Keg, on_delete=models.PROTECT) + keg = models.ForeignKey(Keg, on_delete=models.PROTECT) openingDate = models.DateTimeField(auto_now_add=True) - quantitySold = models.DecimalField(decimal_places=2, max_digits=5) - amountSold = models.DecimalField(decimal_places=2, max_digits=5) - closingDate = models.DateTimeField() + quantitySold = models.DecimalField(decimal_places=2, max_digits=5, default=0) + amountSold = models.DecimalField(decimal_places=2, max_digits=5, default=0) + closingDate = models.DateTimeField(null=True, blank=True) isCurrentKegHistory = models.BooleanField(default=True) + def __str__(self): + res = "Fût de " + str(self.keg) + " (" + str(self.openingDate) + " - " + if(self.closingDate): + res += str(self.closingDate) + ")" + else: + res += "?)" + return res + class Reload(models.Model): customer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="reload_taken", verbose_name="Client") amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Montant") @@ -152,3 +167,11 @@ class ConsumptionHistory(models.Model): def __str__(self): return "{0} {1} consommé par {2} le {3} (encaissé par {4})".format(self.quantity, self.product, self.customer, self.date, self.coopeman) + +class Consumption(models.Model): + customer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="consumption_global_taken") + product = models.ForeignKey(Product, on_delete=models.PROTECT) + quantity = models.PositiveIntegerField(default=0) + + def __str__(self): + return "Consommation de " + str(self.customer) + " concernant le produit " + str(self.product) diff --git a/gestion/templates/gestion/kegh.html b/gestion/templates/gestion/kegh.html new file mode 100644 index 0000000..2842487 --- /dev/null +++ b/gestion/templates/gestion/kegh.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% block entete %}

Gestion des produits

{% endblock %} +{% block navbar%} + +{% endblock %} +{% block content %} +
+
+

Historique du fût {{ keg.name }}

+
+ Retour à la liste des fûts

+ {% for kegH in kegHistory %} +

Du {{kegH.openingDate}} au {{kegH.closingDate | default:"?"}}

+ Quantité vendue : {{ kegH.quantitySold }} L
+ Montant vendu : {{ kegH.amountSold }} €(prix fût : {{keg.amount}} €)

+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/gestion/templates/gestion/kegs_list.html b/gestion/templates/gestion/kegs_list.html new file mode 100644 index 0000000..7a95d72 --- /dev/null +++ b/gestion/templates/gestion/kegs_list.html @@ -0,0 +1,89 @@ +{% extends 'base.html' %} +{% block entete %}

Gestion des produits

{% endblock %} +{% block navbar%} + +{% endblock %} +{% block content %} +
+
+

Liste des fûts actifs

+
+ Créer un fût + Percuter un fût + Fermer un fût +

+
+ + + + + + + + + + + + + + + + {% for kegH in kegs_active %} + + + + + + + + + + + + {% endfor %} + +
NomStock en souteCode barreCapacitéQuantité vendueMontant venduPrix du fûtHistoriqueAdministrer
{{ kegH.keg.name }}{{ kegH.keg.stockHold}}{{ kegH.keg.barcode }}{{ kegH.keg.capacity }} L{{ kegH.quantitySold }} L{{ kegH.amountSold }} €{{ kegH.keg.amount }} €VoirFermer Modifier
+
+
+
+
+

Liste des fûts inactifs

+
+ Créer un fût + Percuter un fût + Fermer un fût +

+
+ + + + + + + + + + + + + + {% for keg in kegs_inactive %} + + + + + + + + + + {% endfor %} + +
NomStock en souteCode barreCapacitéPrix du fûtHistoriqueAdministrer
{{ keg.name }}{{ keg.stockHold}}{{ keg.barcode }}{{ keg.capacity }} L{{ keg.amount }} €Voir{% if keg.stockHold > 0 %}Percuter{% endif %} Modifier
+
+
+{% endblock %} diff --git a/gestion/templates/gestion/products_index.html b/gestion/templates/gestion/products_index.html index 049d3cf..ff76bc6 100644 --- a/gestion/templates/gestion/products_index.html +++ b/gestion/templates/gestion/products_index.html @@ -27,9 +27,9 @@ Actions possibles :
diff --git a/gestion/templates/gestion/products_list.html b/gestion/templates/gestion/products_list.html index eb96d41..d6ee969 100644 --- a/gestion/templates/gestion/products_list.html +++ b/gestion/templates/gestion/products_list.html @@ -2,10 +2,7 @@ {% block entete %}

Gestion des produits

{% endblock %} {% block navbar%} {% endblock %} {% block content %} diff --git a/gestion/urls.py b/gestion/urls.py index a400287..c861b33 100644 --- a/gestion/urls.py +++ b/gestion/urls.py @@ -11,6 +11,13 @@ urlpatterns = [ path('productsList', views.productsList, name="productsList"), path('addProduct', views.addProduct, name="addProduct"), path('addKeg', views.addKeg, name="addKeg"), + path('openKeg', views.openKeg, name="openKeg"), + path('closeKeg', views.closeKeg, name="closeKeg"), + path('kegsList', views.kegsList, name="kegsList"), + path('kegH/', views.kegH, name="kegH"), + path('editKeg/', views.editKeg, name="editKeg"), + path('openDirectKeg/', views.openDirectKeg, name="openDirectKeg"), + path('closeDirectKeg/', views.closeDirectKeg, name="closeDirectKeg"), path('addMenu', views.addMenu, name="addMenu"), path('getProduct/', views.getProduct, name="getProduct"), path('order', views.order, name="order"), @@ -19,4 +26,7 @@ urlpatterns = [ path('searchProduct', views.searchProduct, name="searchProduct"), path('productProfile/', views.productProfile, name="productProfile"), 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"), + ] \ No newline at end of file diff --git a/gestion/views.py b/gestion/views.py index bd4d23b..b9e83dc 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -5,6 +5,7 @@ from django.http import HttpResponse, Http404 from django.contrib.auth.models import User from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required, permission_required +from django.utils import timezone from coopeV3.acl import active_required, acl_or @@ -12,8 +13,8 @@ import simplejson as json from dal import autocomplete from decimal import * -from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm -from .models import Product, Menu, Keg, ConsumptionHistory +from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm +from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption from preferences.models import PaymentMethod @active_required @@ -44,26 +45,62 @@ def manage(request): @permission_required('gestion.add_consumptionhistory') @csrf_exempt def order(request): - print(request.POST) if("user" not in request.POST or "paymentMethod" not in request.POST or "amount" not in request.POST or "order" not in request.POST): - raise Http404("Erreur du POST") + return HttpResponse("Erreur du POST") else: user = get_object_or_404(User, pk=request.POST['user']) paymentMethod = get_object_or_404(PaymentMethod, pk=request.POST['paymentMethod']) amount = Decimal(request.POST['amount']) order = json.loads(request.POST["order"]) if(len(order) == 0 or amount == 0): - raise Http404("Pas de commande") + return HttpResponse("Pas de commande") + adherentRequired = False + for o in order: + product = get_object_or_404(Product, pk=o["pk"]) + adherentRequired = adherentRequired or product.adherentRequired + if(adherentRequired and not user.profile.is_adherent): + return HttpResponse("N'est pas adhérent et devrait l'être") if(paymentMethod.affect_balance): if(user.profile.balance < amount): - raise Http404("Solde inférieur au prix de la commande") + return HttpResponse("Solde inférieur au prix de la commande") else: user.profile.debit += amount user.save() for o in order: - print(o) product = get_object_or_404(Product, pk=o["pk"]) - ch = ConsumptionHistory(customer = user, quantity = int(o["quantity"]), paymentMethod=paymentMethod, product=product, amount=int(o["quantity"])*product.amount, coopeman=request.user) + quantity = int(o["quantity"]) + if(product.category == Product.P_PRESSION): + keg = get_object_or_404(Keg, pinte=product) + if(not keg.is_active): + return HttpResponse("Une erreur inconnue s'est produite") + kegHistory = get_object_or_404(KegHistory, keg=keg, isCurrentKegHistory=True) + kegHistory.quantitySold += Decimal(quantity * 0.5) + kegHistory.amountSold += Decimal(quantity * product.amount) + kegHistory.save() + elif(product.category == Product.D_PRESSION): + keg = get_object_or_404(Keg, demi=product) + if(not keg.is_active): + return HttpResponse("Une erreur inconnue s'est produite") + kegHistory = get_object_or_404(KegHistory, keg=keg, isCurrentKegHistory=True) + kegHistory.quantitySold += Decimal(quantity * 0.25) + kegHistory.amountSold += Decimal(quantity * product.amount) + kegHistory.save() + elif(product.category == Product.G_PRESSION): + keg = get_object_or_404(Keg, galopin=product) + if(not keg.is_active): + return HttpResponse("Une erreur inconnue s'est produite") + kegHistory = get_object_or_404(KegHistory, keg=keg, isCurrentKegHistory=True) + kegHistory.quantitySold += Decimal(quantity * 0.125) + kegHistory.amountSold += Decimal(quantity * product.amount) + kegHistory.save() + else: + if(product.stockHold > 0): + product.stockHold -= 1 + product.save() + consumption, _ = Consumption.objects.get_or_create(customer=user, product=product) + consumption.quantity += quantity + consumption.save() + ch = ConsumptionHistory(customer = user, quantity = quantity, paymentMethod=paymentMethod, product=product, amount=int(o["quantity"])*product.amount, coopeman=request.user) ch.save() return HttpResponse("La commande a bien été effectuée") @@ -163,9 +200,120 @@ def addKeg(request): if(form.is_valid()): keg = form.save() messages.success(request, "Le fût " + keg.name + " a bien été ajouté") - return redirect(reverse('gestion:productsIndex')) + return redirect(reverse('gestion:kegsList')) return render(request, "form.html", {"form":form, "form_title": "Ajout d'un fût", "form_button": "Ajouter"}) +@login_required +@permission_required('gestion.edit_keg') +def editKeg(request, pk): + keg = get_object_or_404(Keg, pk=pk) + form = KegForm(request.POST or None, instance=keg) + if(form.is_valid()): + form.save() + messages.success(request, "Le fût a bien été modifié") + return redirect(reverse('gestion:kegsList')) + return render(request, "form.html", {"form": form, "form_title": "Modification d'un fût", "form_button": "Modifier"}) + +@login_required +@permission_required('gestion.open_keg') +def openKeg(request): + form = SelectPositiveKegForm(request.POST or None) + if(form.is_valid()): + keg = form.cleaned_data['keg'] + previousKegHistory = KegHistory.objects.filter(keg=keg).filter(isCurrentKegHistory=True) + for pkh in previousKegHistory: + pkh.isCurrentKegHistory = False + pkh.closingDate = timezone.now() + pkh.save() + kegHistory = KegHistory(keg = keg) + kegHistory.save() + keg.stockHold -= 1 + keg.is_active = True + keg.save() + messages.success(request, "Le fut a bien été percuté") + return redirect(reverse('gestion:kegsList')) + return render(request, "form.html", {"form": form, "form_title":"Percutage d'un fût", "form_button":"Percuter"}) + +@login_required +@permission_required('gestion.open_keg') +def openDirectKeg(request, pk): + keg = get_object_or_404(Keg, pk=pk) + if(keg.stockHold > 0): + previousKegHistory = KegHistory.objects.filter(keg=keg).filter(isCurrentKegHistory=True) + for pkh in previousKegHistory: + pkh.isCurrentKegHistory = False + pkh.closingDate = timezone.now() + pkh.save() + kegHistory = KegHistory(keg = keg) + kegHistory.save() + keg.stockHold -= 1 + keg.is_active = True + keg.save() + messages.success(request, "Le fût a bien été percuté") + else: + messages.error(request, "Il n'y a pas de fût en stock") + return redirect(reverse('gestion:kegsList')) + +@login_required +@permission_required('gestion.close_keg') +def closeKeg(request): + form = SelectActiveKegForm(request.POST or None) + if(form.is_valid()): + keg = form.cleaned_data['keg'] + kegHistory = get_object_or_404(KegHistory, keg=keg, isCurrentKegHistory=True) + kegHistory.isCurrentKegHistory = False + kegHistory.closingDate = timezone.now() + kegHistory.save() + keg.is_active = False + keg.save() + messages.success(request, "Le fût a bien été fermé") + return redirect(reverse('gestion:kegsList')) + return render(request, "form.html", {"form": form, "form_title":"Fermeture d'un fût", "form_button":"Fermer le fût"}) + +@login_required +@permission_required('gestion:close_keg') +def closeDirectKeg(request, pk): + keg = get_object_or_404(Keg, pk=pk) + if(keg.is_active): + kegHistory = get_object_or_404(KegHistory, keg=keg, isCurrentKegHistory=True) + kegHistory.isCurrentKegHistory = False + kegHistory.closingDate = timezone.now() + kegHistory.save() + keg.is_active = False + keg.save() + messages.success(request, "Le fût a bien été fermé") + else: + messages.error(request, "Le fût n'est pas ouvert") + return redirect(reverse('gestion:kegsList')) + +@login_required +@permission_required('gestion.view_keg') +def kegsList(request): + kegs_active = KegHistory.objects.filter(isCurrentKegHistory=True) + ids_actives = kegs_active.values('id') + kegs_inactive = Keg.objects.exclude(id__in = ids_actives) + return render(request, "gestion/kegs_list.html", {"kegs_active": kegs_active, "kegs_inactive": kegs_inactive}) + +@login_required +@permission_required('gestion.view_keghistory') +def kegH(request, pk): + keg = get_object_or_404(Keg, pk=pk) + kegHistory = KegHistory.objects.filter(keg=keg).order_by('-openingDate') + return render(request, "gestion/kegh.html", {"keg": keg, "kegHistory": kegHistory}) + +class KegActiveAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + qs = Keg.objects.filter(is_active = True) + if self.q: + qs = qs.filter(name__istartswith=self.q) + return qs + +class KegPositiveAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + qs = Keg.objects.filter(stockHold__gt = 0) + if self.q: + qs = qs.filter(name__istartswith=self.q) + return qs ########## Menus ########## diff --git a/staticfiles/manage.js b/staticfiles/manage.js index 5d1c896..f37b4af 100644 --- a/staticfiles/manage.js +++ b/staticfiles/manage.js @@ -74,6 +74,5 @@ $(document).ready(function(){ alert("Impossible d'effectuer la transaction"); location.reload(); }); - }); }); diff --git a/users/migrations/0003_auto_20181123_0229.py b/users/migrations/0003_auto_20181123_0229.py new file mode 100644 index 0000000..87c2837 --- /dev/null +++ b/users/migrations/0003_auto_20181123_0229.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1 on 2018-11-23 01:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_auto_20181009_1119'), + ] + + operations = [ + migrations.AlterModelOptions( + name='cotisationhistory', + options={'permissions': (('validate_consumptionhistory', 'Peut (in)valider les cotisations'),)}, + ), + ] diff --git a/users/models.py b/users/models.py index 58e87d4..8c95f92 100644 --- a/users/models.py +++ b/users/models.py @@ -2,6 +2,7 @@ from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver +from django.utils import timezone from preferences.models import PaymentMethod, Cotisation from gestion.models import ConsumptionHistory @@ -50,6 +51,13 @@ class Profile(models.Model): school = models.ForeignKey(School, on_delete=models.PROTECT, blank=True, null=True) cotisationEnd = models.DateTimeField(blank=True, null=True) + @property + def is_adherent(self): + if(self.cotisationEnd and self.cotisationEnd > timezone.now()): + return True + else: + return False + @property def balance(self): return self.credit - self.debit diff --git a/users/views.py b/users/views.py index 2d36ee5..893a7f8 100644 --- a/users/views.py +++ b/users/views.py @@ -627,8 +627,8 @@ def addCotisationHistory(request, pk): if(form.is_valid()): cotisation = form.save(commit=False) if(cotisation.paymentMethod.affect_balance): - if(user.profile.balance >= cotisation.amount): - user.profile.balance -= cotisation.amount + if(user.profile.balance >= cotisation.cotisation.amount): + user.profile.debit += cotisation.cotisation.amount else: cotisation.delete() messages.error(request, "Solde insuffisant")