Merge branch 'master' into aureplop/kfet_config

This commit is contained in:
Aurélien Delobelle 2017-04-03 15:22:03 +02:00
commit a4a854bc50
23 changed files with 454 additions and 164 deletions

View file

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

View file

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

View file

@ -47,4 +47,6 @@ urlpatterns = [
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
name='bda-descriptions'),
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
name='bda-catalogue'),
]

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = [
]

View file

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

View file

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

View file

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

View file

@ -134,7 +134,10 @@ function getErrorsHtml(data) {
content += '</ul>';
}
if ('account' in data['errors']) {
content += data['errors']['account'];
content += 'Général';
content += '<ul>';
content += '<li>Opération invalide sur le compte '+data['errors']['account']+'</li>';
content += '</ul>';
}
return content;
}

View file

@ -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 %}
<form submit="" method="post">
{% csrf_token %}
{{ form.as_p }}
{% if not perms.kfet.add_article %}
<input type="password" name="KFETPASSWORD">
{% endif %}
<input type="submit" value="Enregistrer">
</form>
{% include "kfet/base_messages.html" %}
<div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% 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" %}
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -101,9 +101,8 @@
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
<script>
jQuery(document).ready(function() {
var stat_last = $("#stat_last");
var stat_last_url = "{% url 'kfet.article.stat.last' article.id %}";
STAT.get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
var stat_last = new StatsGroup("{% url 'kfet.article.stat.last' article.id %}",
$("#stat_last"));
});
</script>
{% endblock %}

View file

@ -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 %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
{% if not perms.kfet.change_article %}
<input type="password" name="KFETPASSWORD">
{% endif %}
<input type="submit" value="Mettre à jour">
</form>
{% include "kfet/base_messages.html" %}
<div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% 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" %}
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -80,7 +80,7 @@
<td><input id="id_taken_001" name="taken_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
</tr>
</table>
Chèque: <input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required>
<p style="font-weight:bold"> Chèque:</p> <input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required>
</div>
</div>
<div class="content-right-block">

View file

@ -15,15 +15,15 @@ Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
</div>
<div class="col-sm-8 col-md-9 col-content-right">
{% include 'kfet/base_messages.html' %}
<div class="content-right">
<div class="content-right-block">
<form action="" method="post">
<div class="content-right form-only">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% csrf_token %}
{{ form.as_p }}
{% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.change_checkoutstatement %}
<input type="password" name="KFETPASSWORD">
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
<input type="submit" value="Enregistrer">
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
</form>
</div>
</div>

View file

@ -4,6 +4,11 @@
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-10">
{{ field|add_class:'form-control' }}
<span class="help-block">{{ field.errors }}</span>
{% if field.errors %}
<span class="help-block">{{field.errors}}</span>
{% endif %}
{% if field.help_text %}
<span class="help-block">{{field.help_text}}</span>
{% endif %}
</div>
</div>

View file

@ -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: '<input type="number" lang="en" step="0.01" min="0.01" on autofocus placeholder="€">',
title: 'Montant de la charge',
content: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
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: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
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: '<input type="number" lang="en" step="0.01" min="0.01" on autofocus placeholder="€">',
content: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
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

View file

@ -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 @@
<td>{{ form.v_prev }}</td>
<td>{{ form.stock }}</td>
<td>{{ form.c_rec }}</td>
<td>{{ form.quantity_ordered }}</td>
<td>{{ form.quantity_ordered | add_class:"form-control" }}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -1,4 +1,5 @@
{% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% block title %}{% endblock %}
{% block content-header-title %}{% endblock %}
@ -33,11 +34,11 @@
<tr>
{{ form.article }}
<td>{{ form.name }}</td>
<td>{{ form.price_HT }}</td>
<td>{{ form.TVA }}</td>
<td>{{ form.rights }}</td>
<td>{{ form.price_HT | add_class:"form-control" }}</td>
<td>{{ form.TVA | add_class:"form-control" }}</td>
<td>{{ form.rights | add_class:"form-control" }}</td>
<td>{{ form.quantity_ordered }}</td>
<td>{{ form.quantity_received }}</td>
<td>{{ form.quantity_received | add_class:"form-control" }}</td>
{% endfor %}
</tbody>
</table>

View file

@ -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' %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
{% if not perms.kfet.change_supplier %}
<input type="password" name="KFETPASSWORD">
{% endif %}
<input type="submit" class="btn btn-primary btn-lg">
</form>
<div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% 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" %}
</form>
</div>
</div>
</div>
{% endblock %}

View file

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