3
0
Fork 0
mirror of https://github.com/nanoy42/coope synced 2024-12-23 23:43:47 +00:00

Gestion des f^uts

This commit is contained in:
Yoann Pétri 2018-11-25 13:52:32 +01:00
parent ca6c938e20
commit a45d7746f5
16 changed files with 426 additions and 26 deletions

View file

@ -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)
admin.site.register(ConsumptionHistory)
admin.site.register(KegHistory)
admin.site.register(Consumption)

View file

@ -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}))
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'))

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -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')),
],
),
]

View file

@ -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)

View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% block entete %}<h1>Gestion des produits</h1>{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Historique du fût</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Historique du fût {{ keg.name }}</h2>
</header>
<a href="{% url 'gestion:kegsList' %}">Retour à la liste des fûts</a><br><br>
{% for kegH in kegHistory %}
<h2>Du {{kegH.openingDate}} au {{kegH.closingDate | default:"?"}}</h2>
Quantité vendue : {{ kegH.quantitySold }} L<br>
Montant vendu : {{ kegH.amountSold }} €(prix fût : {{keg.amount}} €)<br><br>
{% endfor %}
</section>
{% endblock %}

View file

@ -0,0 +1,89 @@
{% extends 'base.html' %}
{% block entete %}<h1>Gestion des produits</h1>{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Liste des fûts actifs</a></li>
<li><a href="#second">Liste des fûts inactifs</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des fûts actifs</h2>
</header>
<a class="button" href="{% url 'gestion:addKeg' %}">Créer un fût</a>
<a class="button" href="{% url 'gestion:openKeg' %}">Percuter un fût</a>
<a class="button" href="{% url 'gestion:closeKeg' %}">Fermer un fût</a>
<br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>Stock en soute</th>
<th>Code barre</th>
<th>Capacité</th>
<th>Quantité vendue</th>
<th>Montant vendu</th>
<th>Prix du fût</th>
<th>Historique</th>
<th>Administrer</th>
</tr>
</thead>
<tbody>
{% for kegH in kegs_active %}
<tr>
<td>{{ kegH.keg.name }}</td>
<td>{{ kegH.keg.stockHold}}</td>
<td>{{ kegH.keg.barcode }}</td>
<td>{{ kegH.keg.capacity }} L</td>
<td>{{ kegH.quantitySold }} L</td>
<td>{{ kegH.amountSold }} €</td>
<td>{{ kegH.keg.amount }} €</td>
<td><a href="{% url 'gestion:kegH' kegH.keg.pk %}">Voir</a></td>
<td><a href="{% url 'gestion:closeDirectKeg' kegH.keg.pk %}" class="button small">Fermer</a> <a href="{% url 'gestion:editKeg' kegH.keg.pk %}" class="button small">Modifier</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section id="first" class="main">
<header class="major">
<h2>Liste des fûts inactifs</h2>
</header>
<a class="button" href="{% url 'gestion:addKeg' %}">Créer un fût</a>
<a class="button" href="{% url 'gestion:openKeg' %}">Percuter un fût</a>
<a class="button" href="{% url 'gestion:closeKeg' %}">Fermer un fût</a>
<br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>Stock en soute</th>
<th>Code barre</th>
<th>Capacité</th>
<th>Prix du fût</th>
<th>Historique</th>
<th>Administrer</th>
</tr>
</thead>
<tbody>
{% for keg in kegs_inactive %}
<tr>
<td>{{ keg.name }}</td>
<td>{{ keg.stockHold}}</td>
<td>{{ keg.barcode }}</td>
<td>{{ keg.capacity }} L</td>
<td>{{ keg.amount }} €</td>
<td><a href="{% url 'gestion:kegH' keg.pk %}">Voir</a></td>
<td>{% if keg.stockHold > 0 %}<a href="{% url 'gestion:openDirectKeg' keg.pk %}" class="button small">Percuter</a>{% endif %} <a href="{% url 'gestion:editKeg' keg.pk %}" class="button small">Modifier</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -27,9 +27,9 @@
Actions possibles :
<ul>
<li><a href="{% url 'gestion:addKeg' %}">Créer un fut</a></li>
<li><a href="">Percuter un fut</a></li>
<li><a href="">Lister les futs</a></li>
<li><a href="">Historique des futs</a></li>
<li><a href="{% url 'gestion:openKeg' %}">Percuter un fut</a></li>
<li><a href="{% url 'gestion:closeKeg' %}">Fermer un fût</a></li>
<li><a href="{% url 'gestion:kegsList' %}">Lister les futs</a></li>
</ul>
</section>
<section id="third" class="main">

View file

@ -2,10 +2,7 @@
{% block entete %}<h1>Gestion des produits</h1>{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Produits</a></li>
<li><a href="#second">Futs</a></li>
<li><a href="#third">Menus</a></li>
<li><a href="#fourth">Stocks</a></li>
<li><a href="#first">Liste des produits</a></li>
</ul>
{% endblock %}
{% block content %}

View file

@ -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/<int:pk>', views.kegH, name="kegH"),
path('editKeg/<int:pk>', views.editKeg, name="editKeg"),
path('openDirectKeg/<int:pk>', views.openDirectKeg, name="openDirectKeg"),
path('closeDirectKeg/<int:pk>', views.closeDirectKeg, name="closeDirectKeg"),
path('addMenu', views.addMenu, name="addMenu"),
path('getProduct/<str:barcode>', views.getProduct, name="getProduct"),
path('order', views.order, name="order"),
@ -19,4 +26,7 @@ urlpatterns = [
path('searchProduct', views.searchProduct, name="searchProduct"),
path('productProfile/<int:pk>', 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"),
]

View file

@ -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 ##########

View file

@ -74,6 +74,5 @@ $(document).ready(function(){
alert("Impossible d'effectuer la transaction");
location.reload();
});
});
});

View file

@ -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'),)},
),
]

View file

@ -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

View file

@ -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")