diff --git a/bda/migrations/0011_tirage_appear_catalogue.py b/bda/migrations/0011_tirage_appear_catalogue.py new file mode 100644 index 00000000..c2a2479d --- /dev/null +++ b/bda/migrations/0011_tirage_appear_catalogue.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bda', '0010_spectaclerevente_shotgun'), + ] + + operations = [ + migrations.AddField( + model_name='tirage', + name='appear_catalogue', + field=models.BooleanField( + default=False, + verbose_name='Tirage à afficher dans le catalogue' + ), + ), + ] diff --git a/bda/models.py b/bda/models.py index df73a209..15acf686 100644 --- a/bda/models.py +++ b/bda/models.py @@ -18,6 +18,7 @@ class Tirage(models.Model): fermeture = models.DateTimeField("Date et heure de fermerture du tirage") tokens = models.TextField("Graine(s) du tirage", blank=True) active = models.BooleanField("Tirage actif", default=False) + appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False) enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False) @@ -78,6 +79,15 @@ class Spectacle(models.Model): self.price ) + def getImgUrl(self): + """ + Cette fonction permet d'obtenir l'URL de l'image, si elle existe + """ + try: + return self.image.url + except: + return None + def send_rappel(self): """ Envoie un mail de rappel à toutes les personnes qui ont une place pour diff --git a/bda/urls.py b/bda/urls.py index bbbf8e39..df660740 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -47,4 +47,6 @@ urlpatterns = [ url(r'^mails-rappel/(?P\d+)$', views.send_rappel), url(r'^descriptions/(?P\d+)$', views.descriptions_spectacles, name='bda-descriptions'), + url(r'^catalogue/(?P[a-z]+)$', views.catalogue, + name='bda-catalogue'), ] diff --git a/bda/views.py b/bda/views.py index 096aacf5..41e5d08b 100644 --- a/bda/views.py +++ b/bda/views.py @@ -3,11 +3,11 @@ import random import hashlib import time +import json from datetime import timedelta from custommail.shortcuts import ( send_mass_custom_mail, send_custom_mail, render_custom_mail ) - from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib import messages @@ -15,18 +15,24 @@ from django.db import models, transaction from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory -from django.http import HttpResponseBadRequest, HttpResponseRedirect +from django.http import ( + HttpResponseBadRequest, HttpResponseRedirect, JsonResponse +) from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats from django.views.generic.list import ListView - +from django.core.exceptions import ObjectDoesNotExist from gestioncof.decorators import cof_required, buro_required -from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage, SpectacleRevente +from bda.models import ( + Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, + SpectacleRevente, Salle, Quote, CategorieSpectacle +) from bda.algorithm import Algorithm -from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ - InscriptionReventeForm, SoldForm +from bda.forms import ( + BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, + SoldForm +) @cof_required @@ -639,3 +645,98 @@ def descriptions_spectacles(request, tirage_id): return HttpResponseBadRequest( "La variable GET 'location' doit contenir un entier") return render(request, 'descriptions.html', {'shows': shows_qs.all()}) + + +def catalogue(request, request_type): + """ + Vue destinée à communiquer avec un client AJAX, fournissant soit : + - la liste des tirages + - les catégories et salles d'un tirage + - les descriptions d'un tirage (filtrées selon la catégorie et la salle) + """ + if request_type == "list": + # Dans ce cas on retourne la liste des tirages et de leur id en JSON + data_return = list( + Tirage.objects.filter(appear_catalogue=True).values('id', 'title')) + return JsonResponse(data_return, safe=False) + if request_type == "details": + # Dans ce cas on retourne une liste des catégories et des salles + tirage_id = request.GET.get('id', '') + try: + tirage = Tirage.objects.get(id=tirage_id) + except ObjectDoesNotExist: + return HttpResponseBadRequest( + "Aucun tirage correspondant à l'id " + + tirage_id) + except ValueError: + return HttpResponseBadRequest( + "Mauvais format d'identifiant : " + + tirage_id) + categories = list( + CategorieSpectacle.objects.filter( + spectacle__in=tirage.spectacle_set.all()) + .distinct().values('id', 'name')) + locations = list( + Salle.objects.filter( + spectacle__in=tirage.spectacle_set.all()) + .distinct().values('id', 'name')) + data_return = {'categories': categories, 'locations': locations} + return JsonResponse(data_return, safe=False) + if request_type == "descriptions": + # Ici on retourne les descriptions correspondant à la catégorie et + # à la salle spécifiées + + tirage_id = request.GET.get('id', '') + categories = request.GET.get('category', '[0]') + locations = request.GET.get('location', '[0]') + try: + category_id = json.loads(categories) + location_id = json.loads(locations) + tirage = Tirage.objects.get(id=tirage_id) + + shows_qs = tirage.spectacle_set + if not(0 in category_id): + shows_qs = shows_qs.filter( + category__id__in=category_id) + if not(0 in location_id): + shows_qs = shows_qs.filter( + location__id__in=location_id) + except ObjectDoesNotExist: + return HttpResponseBadRequest( + "Impossible de trouver des résultats correspondant " + "à ces caractéristiques : " + + "id = " + tirage_id + + ", catégories = " + categories + + ", salles = " + locations) + except ValueError: # Contient JSONDecodeError + return HttpResponseBadRequest( + "Impossible de parser les paramètres donnés : " + + "id = " + request.GET.get('id', '') + + ", catégories = " + request.GET.get('category', '[0]') + + ", salles = " + request.GET.get('location', '[0]')) + + # On convertit les descriptions à envoyer en une liste facilement + # JSONifiable (il devrait y avoir un moyen plus efficace en + # redéfinissant le serializer de JSON) + data_return = [{ + 'title': spectacle.title, + 'category': str(spectacle.category), + 'date': str(formats.date_format( + timezone.localtime(spectacle.date), + "SHORT_DATETIME_FORMAT")), + 'location': str(spectacle.location), + 'vips': spectacle.vips, + 'description': spectacle.description, + 'slots_description': spectacle.slots_description, + 'quotes': list(Quote.objects.filter(spectacle=spectacle).values( + 'author', 'text')), + 'image': spectacle.getImgUrl(), + 'ext_link': spectacle.ext_link, + 'price': spectacle.price, + 'slots': spectacle.slots + } + for spectacle in shows_qs.all() + ] + return JsonResponse(data_return, safe=False) + # Si la requête n'est pas de la forme attendue, on quitte avec une erreur + return HttpResponseBadRequest() diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py index a9abbad7..3532525d 100644 --- a/gestioncof/autocomplete.py +++ b/gestioncof/autocomplete.py @@ -14,6 +14,10 @@ from gestioncof.decorators import buro_required class Clipper(object): def __init__(self, clipper, fullname): + if fullname is None: + fullname = "" + assert isinstance(clipper, str) + assert isinstance(fullname, str) self.clipper = clipper self.fullname = fullname @@ -62,17 +66,19 @@ def autocomplete(request): )) if ldap_query != "(&)": # If none of the bits were legal, we do not perform the query + entries = None with Connection(settings.LDAP_SERVER_URL) as conn: conn.search( 'dc=spi,dc=ens,dc=fr', ldap_query, attributes=['uid', 'cn'] ) - queries['clippers'] = conn.entries + entries = conn.entries # Clearing redundancies queries['clippers'] = [ - Clipper(clipper.uid, clipper.cn) - for clipper in queries['clippers'] - if str(clipper.uid) not in usernames + Clipper(entry.uid.value, entry.cn.value) + for entry in entries + if entry.uid.value + and entry.uid.value not in usernames ] # Resulting data diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py index bee99517..09057d4a 100644 --- a/kfet/autocomplete.py +++ b/kfet/autocomplete.py @@ -13,6 +13,10 @@ from kfet.models import Account class Clipper(object): def __init__(self, clipper, fullname): + if fullname is None: + fullname = "" + assert isinstance(clipper, str) + assert isinstance(fullname, str) self.clipper = clipper self.fullname = fullname @@ -80,17 +84,19 @@ def account_create(request): )) if ldap_query != "(&)": # If none of the bits were legal, we do not perform the query + entries = None with Connection(settings.LDAP_SERVER_URL) as conn: conn.search( 'dc=spi,dc=ens,dc=fr', ldap_query, attributes=['uid', 'cn'] ) - queries['clippers'] = conn.entries + entries = conn.entries # Clearing redundancies queries['clippers'] = [ - Clipper(clipper.uid, clipper.cn) - for clipper in queries['clippers'] - if str(clipper.uid) not in usernames + Clipper(entry.uid.value, entry.cn.value) + for entry in entries + if entry.uid.value + and entry.uid.value not in usernames ] # Resulting data diff --git a/kfet/forms.py b/kfet/forms.py index 2b59e1b3..0fc02dd3 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -74,8 +74,11 @@ class AccountRestrictForm(AccountForm): class AccountPwdForm(forms.Form): pwd1 = forms.CharField( + label="Mot de passe K-Fêt", + help_text="Le mot de passe doit contenir au moins huit caractères", widget=forms.PasswordInput) pwd2 = forms.CharField( + label="Confirmer le mot de passe", widget=forms.PasswordInput) def clean(self): @@ -128,6 +131,7 @@ class UserRestrictTeamForm(UserForm): class UserGroupForm(forms.ModelForm): groups = forms.ModelMultipleChoiceField( Group.objects.filter(name__icontains='K-Fêt'), + label='Statut équipe', required=False) def clean_groups(self): @@ -235,16 +239,20 @@ class CheckoutStatementUpdateForm(forms.ModelForm): class ArticleForm(forms.ModelForm): category_new = forms.CharField( + label="Créer une catégorie", max_length=45, required = False) category = forms.ModelChoiceField( + label="Catégorie", queryset = ArticleCategory.objects.all(), required = False) suppliers = forms.ModelMultipleChoiceField( + label="Fournisseurs", queryset = Supplier.objects.all(), required = False) supplier_new = forms.CharField( + label="Créer un fournisseur", max_length = 45, required = False) @@ -318,11 +326,10 @@ class KPsulOperationForm(forms.ModelForm): widget = forms.HiddenInput()) class Meta: model = Operation - fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb'] + fields = ['type', 'amount', 'article', 'article_nb'] widgets = { 'type': forms.HiddenInput(), 'amount': forms.HiddenInput(), - 'is_checkout': forms.HiddenInput(), 'article_nb': forms.HiddenInput(), } @@ -338,7 +345,6 @@ class KPsulOperationForm(forms.ModelForm): "Un achat nécessite un article et une quantité") if article_nb < 1: raise ValidationError("Impossible d'acheter moins de 1 article") - self.cleaned_data['is_checkout'] = True elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]: if not amount or article or article_nb: raise ValidationError("Bad request") @@ -478,9 +484,7 @@ class OrderArticleForm(forms.Form): queryset=Article.objects.all(), widget=forms.HiddenInput(), ) - quantity_ordered = forms.IntegerField( - required=False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + quantity_ordered = forms.IntegerField(required=False) def __init__(self, *args, **kwargs): super(OrderArticleForm, self).__init__(*args, **kwargs) @@ -507,18 +511,14 @@ class OrderArticleToInventoryForm(forms.Form): ) price_HT = forms.DecimalField( max_digits = 7, decimal_places = 4, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) TVA = forms.DecimalField( max_digits = 7, decimal_places = 2, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) rights = forms.DecimalField( max_digits = 7, decimal_places = 4, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) - quantity_received = forms.IntegerField( - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) + quantity_received = forms.IntegerField() def __init__(self, *args, **kwargs): super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs) diff --git a/kfet/migrations/0049_merge.py b/kfet/migrations/0049_merge.py new file mode 100644 index 00000000..0ce9a525 --- /dev/null +++ b/kfet/migrations/0049_merge.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0048_article_hidden'), + ('kfet', '0048_default_datetime'), + ] + + operations = [ + ] diff --git a/kfet/migrations/0050_remove_checkout.py b/kfet/migrations/0050_remove_checkout.py new file mode 100644 index 00000000..f9c374ca --- /dev/null +++ b/kfet/migrations/0050_remove_checkout.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0049_merge'), + ] + + operations = [ + migrations.RemoveField( + model_name='operation', + name='is_checkout', + ), + migrations.AlterField( + model_name='operation', + name='type', + field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index b4af61c1..c039ab06 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -40,7 +40,7 @@ class Account(models.Model): balance = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0) - is_frozen = models.BooleanField(default = False) + is_frozen = models.BooleanField("est gelé", default = False) created_at = models.DateTimeField(auto_now_add = True, null = True) # Optional PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)] @@ -48,6 +48,7 @@ class Account(models.Model): choices = PROMO_CHOICES, blank = True, null = True, default = default_promo()) nickname = models.CharField( + "surnom(s)", max_length = 255, blank = True, default = "") password = models.CharField( @@ -224,14 +225,18 @@ class AccountNegative(models.Model): start = models.DateTimeField( blank = True, null = True, default = None) balance_offset = models.DecimalField( + "décalage de balance", + help_text="Montant non compris dans l'autorisation de négatif", max_digits = 6, decimal_places = 2, blank = True, null = True, default = None) authz_overdraft_amount = models.DecimalField( + "négatif autorisé", max_digits = 6, decimal_places = 2, blank = True, null = True, default = None) authz_overdraft_until = models.DateTimeField( + "expiration du négatif", blank = True, null = True, default = None) - comment = models.CharField(max_length = 255, blank = True) + comment = models.CharField("commentaire", max_length = 255, blank = True) @python_2_unicode_compatible class Checkout(models.Model): @@ -273,29 +278,35 @@ class CheckoutStatement(models.Model): checkout = models.ForeignKey( Checkout, on_delete = models.PROTECT, related_name = "statements") - balance_old = models.DecimalField(max_digits = 6, decimal_places = 2) - balance_new = models.DecimalField(max_digits = 6, decimal_places = 2) - amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2) - amount_error = models.DecimalField(max_digits = 6, decimal_places = 2) + balance_old = models.DecimalField("ancienne balance", + max_digits = 6, decimal_places = 2) + balance_new = models.DecimalField("nouvelle balance", + max_digits = 6, decimal_places = 2) + amount_taken = models.DecimalField("montant pris", + max_digits = 6, decimal_places = 2) + amount_error = models.DecimalField("montant de l'erreur", + max_digits = 6, decimal_places = 2) at = models.DateTimeField(auto_now_add = True) - not_count = models.BooleanField(default=False) + not_count = models.BooleanField("caisse non comptée", default=False) - taken_001 = models.PositiveSmallIntegerField(default=0) - taken_002 = models.PositiveSmallIntegerField(default=0) - taken_005 = models.PositiveSmallIntegerField(default=0) - taken_01 = models.PositiveSmallIntegerField(default=0) - taken_02 = models.PositiveSmallIntegerField(default=0) - taken_05 = models.PositiveSmallIntegerField(default=0) - taken_1 = models.PositiveSmallIntegerField(default=0) - taken_2 = models.PositiveSmallIntegerField(default=0) - taken_5 = models.PositiveSmallIntegerField(default=0) - taken_10 = models.PositiveSmallIntegerField(default=0) - taken_20 = models.PositiveSmallIntegerField(default=0) - taken_50 = models.PositiveSmallIntegerField(default=0) - taken_100 = models.PositiveSmallIntegerField(default=0) - taken_200 = models.PositiveSmallIntegerField(default=0) - taken_500 = models.PositiveSmallIntegerField(default=0) - taken_cheque = models.DecimalField(default=0, max_digits=6, decimal_places=2) + taken_001 = models.PositiveSmallIntegerField("pièces de 1¢", default=0) + taken_002 = models.PositiveSmallIntegerField("pièces de 2¢", default=0) + taken_005 = models.PositiveSmallIntegerField("pièces de 5¢", default=0) + taken_01 = models.PositiveSmallIntegerField("pièces de 10¢", default=0) + taken_02 = models.PositiveSmallIntegerField("pièces de 20¢", default=0) + taken_05 = models.PositiveSmallIntegerField("pièces de 50¢", default=0) + taken_1 = models.PositiveSmallIntegerField("pièces de 1€", default=0) + taken_2 = models.PositiveSmallIntegerField("pièces de 2€", default=0) + taken_5 = models.PositiveSmallIntegerField("billets de 5€", default=0) + taken_10 = models.PositiveSmallIntegerField("billets de 10€", default=0) + taken_20 = models.PositiveSmallIntegerField("billets de 20€", default=0) + taken_50 = models.PositiveSmallIntegerField("billets de 50€", default=0) + taken_100 = models.PositiveSmallIntegerField("billets de 100€", default=0) + taken_200 = models.PositiveSmallIntegerField("billets de 200€", default=0) + taken_500 = models.PositiveSmallIntegerField("billets de 500€", default=0) + taken_cheque = models.DecimalField( + "montant des chèques", + default=0, max_digits=6, decimal_places=2) def __str__(self): return '%s %s' % (self.checkout, self.at) @@ -336,19 +347,21 @@ class ArticleCategory(models.Model): @python_2_unicode_compatible class Article(models.Model): - name = models.CharField(max_length = 45) - is_sold = models.BooleanField(default = True) - hidden = models.BooleanField(default=False, + name = models.CharField("nom", max_length = 45) + is_sold = models.BooleanField("en vente", default = True) + hidden = models.BooleanField("caché", + default=False, help_text="Si oui, ne sera pas affiché " "au public ; par exemple " "sur la carte.") price = models.DecimalField( + "prix", max_digits = 6, decimal_places = 2, default = 0) stock = models.IntegerField(default = 0) category = models.ForeignKey( ArticleCategory, on_delete = models.PROTECT, - related_name = "articles") + related_name = "articles", verbose_name='catégorie') BOX_TYPE_CHOICES = ( ("caisse", "caisse"), ("carton", "carton"), @@ -356,10 +369,12 @@ class Article(models.Model): ("fût", "fût"), ) box_type = models.CharField( + "type de contenant", choices = BOX_TYPE_CHOICES, max_length = choices_length(BOX_TYPE_CHOICES), blank = True, null = True, default = None) box_capacity = models.PositiveSmallIntegerField( + "capacité du contenant", blank = True, null = True, default = None) def __str__(self): @@ -417,11 +432,11 @@ class Supplier(models.Model): Article, through = 'SupplierArticle', related_name = "suppliers") - name = models.CharField(max_length = 45) - address = models.TextField() - email = models.EmailField() - phone = models.CharField(max_length = 10) - comment = models.TextField() + name = models.CharField("nom", max_length = 45) + address = models.TextField("adresse") + email = models.EmailField("adresse mail") + phone = models.CharField("téléphone", max_length = 10) + comment = models.TextField("commentaire") def __str__(self): return self.name @@ -522,54 +537,63 @@ class OperationGroup(models.Model): class Operation(models.Model): PURCHASE = 'purchase' - DEPOSIT = 'deposit' + DEPOSIT = 'deposit' WITHDRAW = 'withdraw' INITIAL = 'initial' + EDIT = 'edit' TYPE_ORDER_CHOICES = ( (PURCHASE, 'Achat'), (DEPOSIT, 'Charge'), (WITHDRAW, 'Retrait'), (INITIAL, 'Initial'), + (EDIT, 'Édition'), ) group = models.ForeignKey( - OperationGroup, on_delete = models.PROTECT, - related_name = "opes") + OperationGroup, on_delete=models.PROTECT, + related_name="opes") type = models.CharField( - choices = TYPE_ORDER_CHOICES, - max_length = choices_length(TYPE_ORDER_CHOICES)) + choices=TYPE_ORDER_CHOICES, + max_length=choices_length(TYPE_ORDER_CHOICES)) amount = models.DecimalField( - max_digits = 6, decimal_places = 2, - blank = True, default = 0) - is_checkout = models.BooleanField(default = True) + max_digits=6, decimal_places=2, + blank=True, default=0) # Optional article = models.ForeignKey( - Article, on_delete = models.PROTECT, - related_name = "operations", - blank = True, null = True, default = None) + Article, on_delete=models.PROTECT, + related_name="operations", + blank=True, null=True, default=None) article_nb = models.PositiveSmallIntegerField( - blank = True, null = True, default = None) + blank=True, null=True, default=None) canceled_by = models.ForeignKey( - Account, on_delete = models.PROTECT, - related_name = "+", - blank = True, null = True, default = None) + Account, on_delete=models.PROTECT, + related_name="+", + blank=True, null=True, default=None) canceled_at = models.DateTimeField( - blank = True, null = True, default = None) + blank=True, null=True, default=None) addcost_for = models.ForeignKey( - Account, on_delete = models.PROTECT, - related_name = "addcosts", - blank = True, null = True, default = None) + Account, on_delete=models.PROTECT, + related_name="addcosts", + blank=True, null=True, default=None) addcost_amount = models.DecimalField( - max_digits = 6, decimal_places = 2, - blank = True, null = True, default = None) + max_digits=6, decimal_places=2, + blank=True, null=True, default=None) + + @property + def is_checkout(self): + return (self.type == Operation.DEPOSIT or + self.type == Operation.WITHDRAW or + (self.type == Operation.PURCHASE and self.group.on_acc.is_cash) + ) def __str__(self): templates = { self.PURCHASE: "{nb} {article.name} ({amount}€)", - self.DEPOSIT: "charge ({amount})", - self.WITHDRAW: "retrait ({amount})", - self.INITIAL: "initial ({amount})", + self.DEPOSIT: "charge ({amount}€)", + self.WITHDRAW: "retrait ({amount}€)", + self.INITIAL: "initial ({amount}€)", + self.EDIT: "édition ({amount}€)", } return templates[self.type].format(nb=self.article_nb, article=self.article, diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index 291c106d..8559f050 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -31,13 +31,22 @@ function KHistory(options={}) { if (ope['type'] == 'purchase') { infos1 = ope['article_nb']; infos2 = ope['article__name']; - } else if (ope['type'] == 'initial') { - infos1 = parsed_amount.toFixed(2)+'€'; - infos2 = 'Initial'; } else { infos1 = parsed_amount.toFixed(2)+'€'; - infos2 = (ope['type'] == 'deposit') ? 'Charge' : 'Retrait'; - infos2 = ope['is_checkout'] ? infos2 : 'Édition'; + switch (ope['type']) { + case 'initial': + infos2 = 'Initial'; + break; + case 'withdraw': + infos2 = 'Retrait'; + break; + case 'deposit': + infos2 = 'Charge'; + break; + case 'edit': + infos2 = 'Édition'; + break; + } } $ope_html diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 6bd03a1f..cc369e32 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -134,7 +134,10 @@ function getErrorsHtml(data) { content += ''; } if ('account' in data['errors']) { - content += data['errors']['account']; + content += 'Général'; + content += '
    '; + content += '
  • Opération invalide sur le compte '+data['errors']['account']+'
  • '; + content += '
'; } return content; } diff --git a/kfet/templates/kfet/article_create.html b/kfet/templates/kfet/article_create.html index 742756b2..71672f8c 100644 --- a/kfet/templates/kfet/article_create.html +++ b/kfet/templates/kfet/article_create.html @@ -1,17 +1,27 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Nouvel article{% endblock %} {% block content-header-title %}Création d'un article{% endblock %} {% block content %} -
- {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.add_article %} - - {% endif %} - -
+{% include "kfet/base_messages.html" %} + +
+
+
+
+ {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.add_article %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %} +
+
+
+
{% endblock %} diff --git a/kfet/templates/kfet/article_read.html b/kfet/templates/kfet/article_read.html index d4219128..35b484a5 100644 --- a/kfet/templates/kfet/article_read.html +++ b/kfet/templates/kfet/article_read.html @@ -101,9 +101,8 @@ {% endblock %} diff --git a/kfet/templates/kfet/article_update.html b/kfet/templates/kfet/article_update.html index db0107cc..85a29f6b 100644 --- a/kfet/templates/kfet/article_update.html +++ b/kfet/templates/kfet/article_update.html @@ -1,17 +1,27 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Édition de l'article {{ article.name }}{% endblock %} {% block content-header-title %}Article {{ article.name }} - Édition{% endblock %} {% block content %} -
- {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.change_article %} - - {% endif %} - -
+{% include "kfet/base_messages.html" %} + +
+
+
+
+ {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.change_article %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} +
+
+
+
{% endblock %} diff --git a/kfet/templates/kfet/checkoutstatement_create.html b/kfet/templates/kfet/checkoutstatement_create.html index 0edb66ad..8ed57017 100644 --- a/kfet/templates/kfet/checkoutstatement_create.html +++ b/kfet/templates/kfet/checkoutstatement_create.html @@ -80,7 +80,7 @@ - Chèque: +

Chèque:

diff --git a/kfet/templates/kfet/checkoutstatement_update.html b/kfet/templates/kfet/checkoutstatement_update.html index 18d8938f..fd35b959 100644 --- a/kfet/templates/kfet/checkoutstatement_update.html +++ b/kfet/templates/kfet/checkoutstatement_update.html @@ -15,15 +15,15 @@ Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
{% include 'kfet/base_messages.html' %} -
-
-
+
+
+ {% csrf_token %} - {{ form.as_p }} + {% include 'kfet/form_snippet.html' with form=form %} {% if not perms.kfet.change_checkoutstatement %} - + {% include 'kfet/form_authentication_snippet.html' %} {% endif %} - + {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index 92ec385d..0873a481 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -4,6 +4,11 @@
{{ field|add_class:'form-control' }} - {{ field.errors }} + {% if field.errors %} + {{field.errors}} + {% endif %} + {% if field.help_text %} + {{field.help_text}} + {% endif %}
diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index b4a44492..f7246ec3 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -875,15 +875,27 @@ $(document).ready(function() { return (-5 <= stock - nb && stock - nb <= 5); } - function addDeposit(amount, is_checkout=1) { + function addDeposit(amount) { var deposit_basket_html = $(item_basket_default_html); var amount = parseFloat(amount).toFixed(2); - var index = addDepositToFormset(amount, is_checkout); - var text = is_checkout ? 'Charge' : 'Édition'; + var index = addDepositToFormset(amount); deposit_basket_html .attr('data-opeindex', index) .find('.number').text(amount+"€").end() - .find('.name').text(text).end() + .find('.name').text('Charge').end() + .find('.amount').text(amountToUKF(amount, account_data['is_cof'], false)); + basket_container.prepend(deposit_basket_html); + updateBasketRel(); + } + + function addEdit(amount) { + var deposit_basket_html = $(item_basket_default_html); + var amount = parseFloat(amount).toFixed(2); + var index = addEditToFormset(amount); + deposit_basket_html + .attr('data-opeindex', index) + .find('.number').text(amount+"€").end() + .find('.name').text('Édition').end() .find('.amount').text(amountToUKF(amount, account_data['is_cof'], false)); basket_container.prepend(deposit_basket_html); updateBasketRel(); @@ -1046,11 +1058,10 @@ $(document).ready(function() { // Ask deposit or withdraw // ----- - function askDeposit(is_checkout=1) { - var title = is_checkout ? 'Montant de la charge' : "Montant de l'édition"; + function askDeposit() { $.confirm({ - title: title, - content: '', + title: 'Montant de la charge', + content: '', backgroundDismiss: true, animation:'top', closeAnimation:'bottom', @@ -1059,7 +1070,34 @@ $(document).ready(function() { var amount = this.$content.find('input').val(); if (!$.isNumeric(amount) || amount <= 0) return false; - addDeposit(amount, is_checkout); + addDeposit(amount); + }, + onOpen: function() { + var that = this + this.$content.find('input').on('keydown', function(e) { + if (e.keyCode == 13) { + e.preventDefault(); + that.$confirmButton.click(); + } + }); + }, + onClose: function() { this._lastFocused = (articleSelect.val() ? articleNb : articleSelect) ; } + }); + } + + function askEdit() { + $.confirm({ + title: "Montant de l'édition", + content: '', + backgroundDismiss: true, + animation:'top', + closeAnimation:'bottom', + keyboardEnabled: true, + confirm: function() { + var amount = this.$content.find('input').val(); + if (!$.isNumeric(amount)) + return false; + addEdit(amount); }, onOpen: function() { var that = this @@ -1077,7 +1115,7 @@ $(document).ready(function() { function askWithdraw() { $.confirm({ title: 'Montant du retrait', - content: '', + content: '', backgroundDismiss: true, animation:'top', closeAnimation:'bottom', @@ -1124,7 +1162,7 @@ $(document).ready(function() { var mngmt_total_forms = 1; var prefix_regex = /__prefix__/; - function addOperationToFormset(type, amount, article='', article_nb='', is_checkout=1) { + function addOperationToFormset(type, amount, article='', article_nb='') { var operation_html = operation_empty_html.clone(); var index = mngmt_total_forms; @@ -1134,8 +1172,7 @@ $(document).ready(function() { .find('#id_form-__prefix__-type').val(type).end() .find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).end() .find('#id_form-__prefix__-article').val(article).end() - .find('#id_form-__prefix__-article_nb').val(article_nb).end() - .find('#id_form-__prefix__-is_checkout').val(is_checkout); + .find('#id_form-__prefix__-article_nb').val(article_nb).end(); mngmt_total_forms_input.val(index+1); mngmt_total_forms++; @@ -1150,12 +1187,16 @@ $(document).ready(function() { return index; } - function addDepositToFormset(amount, is_checkout=1) { - return addOperationToFormset('deposit', amount, '', '', is_checkout); + function addDepositToFormset(amount) { + return addOperationToFormset('deposit', amount, '', ''); } - function addWithdrawToFormset(amount, is_checkout=1) { - return addOperationToFormset('withdraw', amount, '', '', is_checkout); + function addEditToFormset(amount) { + return addOperationToFormset('edit', amount, '', ''); + } + + function addWithdrawToFormset(amount) { + return addOperationToFormset('withdraw', amount, '', ''); } function addPurchaseToFormset(article_id, article_nb, amount=0) { @@ -1440,7 +1481,7 @@ $(document).ready(function() { return false; case 119: // F8 - Edition - askDeposit(0); + askEdit(); return false; case 120: // F9 - Addcost diff --git a/kfet/templates/kfet/order_create.html b/kfet/templates/kfet/order_create.html index cbd84ba8..1bd6d54f 100644 --- a/kfet/templates/kfet/order_create.html +++ b/kfet/templates/kfet/order_create.html @@ -1,4 +1,5 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} {% block title %}Nouvelle commande{% endblock %} {% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %} @@ -60,7 +61,7 @@ {{ form.v_prev }} {{ form.stock }} {{ form.c_rec }} - {{ form.quantity_ordered }} + {{ form.quantity_ordered | add_class:"form-control" }} {% endfor %} diff --git a/kfet/templates/kfet/order_to_inventory.html b/kfet/templates/kfet/order_to_inventory.html index ab107065..5fe920e9 100644 --- a/kfet/templates/kfet/order_to_inventory.html +++ b/kfet/templates/kfet/order_to_inventory.html @@ -1,4 +1,5 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} {% block title %}{% endblock %} {% block content-header-title %}{% endblock %} @@ -33,11 +34,11 @@ {{ form.article }} {{ form.name }} - {{ form.price_HT }} - {{ form.TVA }} - {{ form.rights }} + {{ form.price_HT | add_class:"form-control" }} + {{ form.TVA | add_class:"form-control" }} + {{ form.rights | add_class:"form-control" }} {{ form.quantity_ordered }} - {{ form.quantity_received }} + {{ form.quantity_received | add_class:"form-control" }} {% endfor %} diff --git a/kfet/templates/kfet/supplier_form.html b/kfet/templates/kfet/supplier_form.html index d1e10a5c..168f74d9 100644 --- a/kfet/templates/kfet/supplier_form.html +++ b/kfet/templates/kfet/supplier_form.html @@ -1,4 +1,6 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Fournisseur - Modification{% endblock %} {% block content-header-title %}Fournisseur - Modification{% endblock %} @@ -7,13 +9,19 @@ {% include 'kfet/base_messages.html' %} -
- {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.change_supplier %} - - {% endif %} - -
+
+
+
+
+ {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.change_supplier %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} +
+
+
+
{% endblock %} diff --git a/kfet/views.py b/kfet/views.py index 823a205d..b4d1328b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -165,8 +165,7 @@ def account_create_special(request): ope = Operation.objects.create( group = opegroup, type = Operation.INITIAL, - amount = amount, - is_checkout = False) + amount = amount) messages.success(request, 'Compte créé : %s' % account.trigramme) return redirect('kfet.account.create') except Account.UserHasAccount as e: @@ -1010,10 +1009,7 @@ def kpsul_perform_operations(request): operation.amount -= operation.addcost_amount to_addcost_for_balance += operation.addcost_amount if operationgroup.on_acc.is_cash: - operation.is_checkout = True to_checkout_balance += -operation.amount - else: - operation.is_checkout = False if operationgroup.on_acc.is_cof: if is_addcost: operation.addcost_amount = operation.addcost_amount / cof_grant_divisor @@ -1021,13 +1017,13 @@ def kpsul_perform_operations(request): to_articles_stocks[operation.article] -= operation.article_nb else: if operationgroup.on_acc.is_cash: - data['errors']['account'] = 'Charge et retrait impossible sur LIQ' - to_checkout_balance += operation.amount + data['errors']['account'] = 'LIQ' + if operation.type != Operation.EDIT: + to_checkout_balance += operation.amount operationgroup.amount += operation.amount if operation.type == Operation.DEPOSIT: required_perms.add('kfet.perform_deposit') - if (not operation.is_checkout - and operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]): + if operation.type == Operation.EDIT: required_perms.add('kfet.edit_balance_account') need_comment = True if operationgroup.on_acc.is_cof: @@ -1124,8 +1120,7 @@ def kpsul_perform_operations(request): ope_data = { 'id': operation.pk, 'type': operation.type, 'amount': operation.amount, 'addcost_amount': operation.addcost_amount, - 'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None, - 'is_checkout': operation.is_checkout, + 'addcost_for__trigramme': operation.addcost_for and addcost_for.trigramme or None, 'article__name': operation.article and operation.article.name or None, 'article_nb': operation.article_nb, 'group_id': operationgroup.pk, @@ -1215,11 +1210,11 @@ def kpsul_cancel_operations(request): .order_by('at') .last()) if not last_statement or last_statement.at < ope.group.at: - if ope.type == Operation.PURCHASE: + if ope.is_checkout: if ope.group.on_acc.is_cash: to_checkouts_balances[ope.group.checkout] -= - ope.amount - else: - to_checkouts_balances[ope.group.checkout] -= ope.amount + else: + to_checkouts_balances[ope.group.checkout] -= ope.amount # Pour les stocks d'articles # Les stocks d'articles dont il y a eu un inventaire depuis la date @@ -1380,7 +1375,6 @@ def history_json(request): 'type' : ope.type, 'amount' : ope.amount, 'article_nb' : ope.article_nb, - 'is_checkout' : ope.is_checkout, 'addcost_amount': ope.addcost_amount, 'canceled_at' : ope.canceled_at, 'article__name':