forked from DGNum/gestioCOF
Merge branch 'master' into Aufinal/inventaires
This commit is contained in:
commit
f46ba0dd6c
27 changed files with 746 additions and 245 deletions
22
bda/migrations/0011_tirage_appear_catalogue.py
Normal file
22
bda/migrations/0011_tirage_appear_catalogue.py
Normal 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'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -18,6 +18,7 @@ class Tirage(models.Model):
|
||||||
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
||||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||||
active = models.BooleanField("Tirage actif", default=False)
|
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é",
|
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
|
@ -78,6 +79,15 @@ class Spectacle(models.Model):
|
||||||
self.price
|
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):
|
def send_rappel(self):
|
||||||
"""
|
"""
|
||||||
Envoie un mail de rappel à toutes les personnes qui ont une place pour
|
Envoie un mail de rappel à toutes les personnes qui ont une place pour
|
||||||
|
@ -315,10 +325,11 @@ class SpectacleRevente(models.Model):
|
||||||
# Envoie un mail aux perdants
|
# Envoie un mail aux perdants
|
||||||
for inscrit in inscrits:
|
for inscrit in inscrits:
|
||||||
if inscrit != winner:
|
if inscrit != winner:
|
||||||
context['acheteur'] = inscrit.user
|
new_context = dict(context)
|
||||||
|
new_context['acheteur'] = inscrit.user
|
||||||
datatuple.append((
|
datatuple.append((
|
||||||
'bda-revente-loser',
|
'bda-revente-loser',
|
||||||
context,
|
new_context,
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
settings.MAIL_DATA['revente']['FROM'],
|
||||||
[inscrit.user.email]
|
[inscrit.user.email]
|
||||||
))
|
))
|
||||||
|
|
|
@ -47,4 +47,6 @@ urlpatterns = [
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||||
name='bda-descriptions'),
|
name='bda-descriptions'),
|
||||||
|
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
||||||
|
name='bda-catalogue'),
|
||||||
]
|
]
|
||||||
|
|
115
bda/views.py
115
bda/views.py
|
@ -3,11 +3,11 @@
|
||||||
import random
|
import random
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import (
|
from custommail.shortcuts import (
|
||||||
send_mass_custom_mail, send_custom_mail, render_custom_mail
|
send_mass_custom_mail, send_custom_mail, render_custom_mail
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -15,18 +15,24 @@ from django.db import models, transaction
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.db.models import Count, Q, Sum
|
from django.db.models import Count, Q, Sum
|
||||||
from django.forms.models import inlineformset_factory
|
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.core.urlresolvers import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone, formats
|
from django.utils import timezone, formats
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from gestioncof.decorators import cof_required, buro_required
|
from gestioncof.decorators import cof_required, buro_required
|
||||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
from bda.models import (
|
||||||
Tirage, SpectacleRevente
|
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
|
||||||
|
SpectacleRevente, Salle, Quote, CategorieSpectacle
|
||||||
|
)
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
from bda.forms import (
|
||||||
InscriptionReventeForm, SoldForm
|
BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm,
|
||||||
|
SoldForm
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
@cof_required
|
||||||
|
@ -639,3 +645,98 @@ def descriptions_spectacles(request, tirage_id):
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
"La variable GET 'location' doit contenir un entier")
|
"La variable GET 'location' doit contenir un entier")
|
||||||
return render(request, 'descriptions.html', {'shows': shows_qs.all()})
|
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()
|
||||||
|
|
|
@ -14,6 +14,10 @@ from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
class Clipper(object):
|
class Clipper(object):
|
||||||
def __init__(self, clipper, fullname):
|
def __init__(self, clipper, fullname):
|
||||||
|
if fullname is None:
|
||||||
|
fullname = ""
|
||||||
|
assert isinstance(clipper, str)
|
||||||
|
assert isinstance(fullname, str)
|
||||||
self.clipper = clipper
|
self.clipper = clipper
|
||||||
self.fullname = fullname
|
self.fullname = fullname
|
||||||
|
|
||||||
|
@ -62,17 +66,19 @@ def autocomplete(request):
|
||||||
))
|
))
|
||||||
if ldap_query != "(&)":
|
if ldap_query != "(&)":
|
||||||
# If none of the bits were legal, we do not perform the query
|
# If none of the bits were legal, we do not perform the query
|
||||||
|
entries = None
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||||
conn.search(
|
conn.search(
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||||
attributes=['uid', 'cn']
|
attributes=['uid', 'cn']
|
||||||
)
|
)
|
||||||
queries['clippers'] = conn.entries
|
entries = conn.entries
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['clippers'] = [
|
queries['clippers'] = [
|
||||||
Clipper(clipper.uid, clipper.cn)
|
Clipper(entry.uid.value, entry.cn.value)
|
||||||
for clipper in queries['clippers']
|
for entry in entries
|
||||||
if str(clipper.uid) not in usernames
|
if entry.uid.value
|
||||||
|
and entry.uid.value not in usernames
|
||||||
]
|
]
|
||||||
|
|
||||||
# Resulting data
|
# Resulting data
|
||||||
|
|
|
@ -215,7 +215,7 @@ def _traitement_other(request, demande, redo):
|
||||||
proposals = proposals.items()
|
proposals = proposals.items()
|
||||||
proposed_for = proposed_for.items()
|
proposed_for = proposed_for.items()
|
||||||
return render(request,
|
return render(request,
|
||||||
"gestiocof/traitement_demande_petit_cours_autre_niveau.html",
|
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
||||||
{"demande": demande,
|
{"demande": demande,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"proposals": proposals,
|
"proposals": proposals,
|
||||||
|
|
|
@ -13,6 +13,10 @@ from kfet.models import Account
|
||||||
|
|
||||||
class Clipper(object):
|
class Clipper(object):
|
||||||
def __init__(self, clipper, fullname):
|
def __init__(self, clipper, fullname):
|
||||||
|
if fullname is None:
|
||||||
|
fullname = ""
|
||||||
|
assert isinstance(clipper, str)
|
||||||
|
assert isinstance(fullname, str)
|
||||||
self.clipper = clipper
|
self.clipper = clipper
|
||||||
self.fullname = fullname
|
self.fullname = fullname
|
||||||
|
|
||||||
|
@ -80,17 +84,19 @@ def account_create(request):
|
||||||
))
|
))
|
||||||
if ldap_query != "(&)":
|
if ldap_query != "(&)":
|
||||||
# If none of the bits were legal, we do not perform the query
|
# If none of the bits were legal, we do not perform the query
|
||||||
|
entries = None
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||||
conn.search(
|
conn.search(
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||||
attributes=['uid', 'cn']
|
attributes=['uid', 'cn']
|
||||||
)
|
)
|
||||||
queries['clippers'] = conn.entries
|
entries = conn.entries
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['clippers'] = [
|
queries['clippers'] = [
|
||||||
Clipper(clipper.uid, clipper.cn)
|
Clipper(entry.uid.value, entry.cn.value)
|
||||||
for clipper in queries['clippers']
|
for entry in entries
|
||||||
if str(clipper.uid) not in usernames
|
if entry.uid.value
|
||||||
|
and entry.uid.value not in usernames
|
||||||
]
|
]
|
||||||
|
|
||||||
# Resulting data
|
# Resulting data
|
||||||
|
|
|
@ -74,8 +74,11 @@ class AccountRestrictForm(AccountForm):
|
||||||
|
|
||||||
class AccountPwdForm(forms.Form):
|
class AccountPwdForm(forms.Form):
|
||||||
pwd1 = forms.CharField(
|
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)
|
widget=forms.PasswordInput)
|
||||||
pwd2 = forms.CharField(
|
pwd2 = forms.CharField(
|
||||||
|
label="Confirmer le mot de passe",
|
||||||
widget=forms.PasswordInput)
|
widget=forms.PasswordInput)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -128,6 +131,7 @@ class UserRestrictTeamForm(UserForm):
|
||||||
class UserGroupForm(forms.ModelForm):
|
class UserGroupForm(forms.ModelForm):
|
||||||
groups = forms.ModelMultipleChoiceField(
|
groups = forms.ModelMultipleChoiceField(
|
||||||
Group.objects.filter(name__icontains='K-Fêt'),
|
Group.objects.filter(name__icontains='K-Fêt'),
|
||||||
|
label='Statut équipe',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
def clean_groups(self):
|
def clean_groups(self):
|
||||||
|
@ -235,16 +239,20 @@ class CheckoutStatementUpdateForm(forms.ModelForm):
|
||||||
|
|
||||||
class ArticleForm(forms.ModelForm):
|
class ArticleForm(forms.ModelForm):
|
||||||
category_new = forms.CharField(
|
category_new = forms.CharField(
|
||||||
|
label="Créer une catégorie",
|
||||||
max_length=45,
|
max_length=45,
|
||||||
required = False)
|
required = False)
|
||||||
category = forms.ModelChoiceField(
|
category = forms.ModelChoiceField(
|
||||||
|
label="Catégorie",
|
||||||
queryset = ArticleCategory.objects.all(),
|
queryset = ArticleCategory.objects.all(),
|
||||||
required = False)
|
required = False)
|
||||||
|
|
||||||
suppliers = forms.ModelMultipleChoiceField(
|
suppliers = forms.ModelMultipleChoiceField(
|
||||||
|
label="Fournisseurs",
|
||||||
queryset = Supplier.objects.all(),
|
queryset = Supplier.objects.all(),
|
||||||
required = False)
|
required = False)
|
||||||
supplier_new = forms.CharField(
|
supplier_new = forms.CharField(
|
||||||
|
label="Créer un fournisseur",
|
||||||
max_length = 45,
|
max_length = 45,
|
||||||
required = False)
|
required = False)
|
||||||
|
|
||||||
|
@ -318,11 +326,10 @@ class KPsulOperationForm(forms.ModelForm):
|
||||||
widget = forms.HiddenInput())
|
widget = forms.HiddenInput())
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Operation
|
model = Operation
|
||||||
fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb']
|
fields = ['type', 'amount', 'article', 'article_nb']
|
||||||
widgets = {
|
widgets = {
|
||||||
'type': forms.HiddenInput(),
|
'type': forms.HiddenInput(),
|
||||||
'amount': forms.HiddenInput(),
|
'amount': forms.HiddenInput(),
|
||||||
'is_checkout': forms.HiddenInput(),
|
|
||||||
'article_nb': forms.HiddenInput(),
|
'article_nb': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +345,6 @@ class KPsulOperationForm(forms.ModelForm):
|
||||||
"Un achat nécessite un article et une quantité")
|
"Un achat nécessite un article et une quantité")
|
||||||
if article_nb < 1:
|
if article_nb < 1:
|
||||||
raise ValidationError("Impossible d'acheter moins de 1 article")
|
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]:
|
elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
|
||||||
if not amount or article or article_nb:
|
if not amount or article or article_nb:
|
||||||
raise ValidationError("Bad request")
|
raise ValidationError("Bad request")
|
||||||
|
@ -479,9 +485,7 @@ class OrderArticleForm(forms.Form):
|
||||||
queryset=Article.objects.all(),
|
queryset=Article.objects.all(),
|
||||||
widget=forms.HiddenInput(),
|
widget=forms.HiddenInput(),
|
||||||
)
|
)
|
||||||
quantity_ordered = forms.IntegerField(
|
quantity_ordered = forms.IntegerField(required=False)
|
||||||
required=False,
|
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(OrderArticleForm, self).__init__(*args, **kwargs)
|
super(OrderArticleForm, self).__init__(*args, **kwargs)
|
||||||
|
@ -508,18 +512,14 @@ class OrderArticleToInventoryForm(forms.Form):
|
||||||
)
|
)
|
||||||
price_HT = forms.DecimalField(
|
price_HT = forms.DecimalField(
|
||||||
max_digits = 7, decimal_places = 4,
|
max_digits = 7, decimal_places = 4,
|
||||||
required = False,
|
required = False)
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
TVA = forms.DecimalField(
|
TVA = forms.DecimalField(
|
||||||
max_digits = 7, decimal_places = 2,
|
max_digits = 7, decimal_places = 2,
|
||||||
required = False,
|
required = False)
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
rights = forms.DecimalField(
|
rights = forms.DecimalField(
|
||||||
max_digits = 7, decimal_places = 4,
|
max_digits = 7, decimal_places = 4,
|
||||||
required = False,
|
required = False)
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
quantity_received = forms.IntegerField()
|
||||||
quantity_received = forms.IntegerField(
|
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)
|
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)
|
||||||
|
|
175
kfet/management/commands/createopes.py
Normal file
175
kfet/management/commands/createopes.py
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
|
||||||
|
"""
|
||||||
|
Crée des opérations aléatoires réparties sur une période de temps spécifiée
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from datetime import timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from kfet.models import (Account, Article, OperationGroup, Operation,
|
||||||
|
Checkout, Transfer, TransferGroup)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Crée des opérations réparties uniformément sur une période de temps"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
# Nombre d'opérations à créer
|
||||||
|
parser.add_argument('opes', type=int,
|
||||||
|
help='Number of opegroups to create')
|
||||||
|
|
||||||
|
# Période sur laquelle créer (depuis num_days avant maintenant)
|
||||||
|
parser.add_argument('days', type=int,
|
||||||
|
help='Period in which to create opegroups')
|
||||||
|
|
||||||
|
# Optionnel : nombre de transfert à créer (défaut 0)
|
||||||
|
parser.add_argument('--transfers', type=int, default=0,
|
||||||
|
help='Number of transfers to create (default 0)')
|
||||||
|
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
|
self.stdout.write("Génération d'opérations")
|
||||||
|
|
||||||
|
# Output log vars
|
||||||
|
opes_created = 0
|
||||||
|
purchases = 0
|
||||||
|
transfers = 0
|
||||||
|
|
||||||
|
num_ops = options['opes']
|
||||||
|
num_transfers = options['transfers']
|
||||||
|
# Convert to seconds
|
||||||
|
time = options['days'] * 24 * 3600
|
||||||
|
|
||||||
|
checkout = Checkout.objects.first()
|
||||||
|
articles = Article.objects.all()
|
||||||
|
accounts = Account.objects.exclude(trigramme='LIQ')
|
||||||
|
liq_account = Account.objects.get(trigramme='LIQ')
|
||||||
|
try:
|
||||||
|
con_account = Account.objects.get(
|
||||||
|
cofprofile__user__first_name='Assurancetourix'
|
||||||
|
)
|
||||||
|
except Account.DoesNotExist:
|
||||||
|
con_account = random.choice(accounts)
|
||||||
|
|
||||||
|
for i in range(num_ops):
|
||||||
|
|
||||||
|
# Randomly pick account
|
||||||
|
if random.random() > 0.25:
|
||||||
|
account = random.choice(accounts)
|
||||||
|
else:
|
||||||
|
account = liq_account
|
||||||
|
|
||||||
|
# Randomly pick time
|
||||||
|
at = timezone.now() - timedelta(
|
||||||
|
seconds=random.randint(0, time))
|
||||||
|
|
||||||
|
# Majoration sur compte 'concert'
|
||||||
|
if random.random() < 0.2:
|
||||||
|
addcost = True
|
||||||
|
addcost_for = con_account
|
||||||
|
addcost_amount = Decimal('0.5')
|
||||||
|
else:
|
||||||
|
addcost = False
|
||||||
|
|
||||||
|
# Initialize opegroup amount
|
||||||
|
amount = Decimal('0')
|
||||||
|
|
||||||
|
opegroup = OperationGroup.objects.create(
|
||||||
|
on_acc=account,
|
||||||
|
checkout=checkout,
|
||||||
|
at=at,
|
||||||
|
is_cof=account.cofprofile.is_cof
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generating operations
|
||||||
|
ope_list = []
|
||||||
|
for j in range(random.randint(1, 4)):
|
||||||
|
# Operation type
|
||||||
|
typevar = random.random()
|
||||||
|
|
||||||
|
# 0.1 probability to have a charge
|
||||||
|
if typevar > 0.9 and account != liq_account:
|
||||||
|
ope = Operation(
|
||||||
|
group=opegroup,
|
||||||
|
type=Operation.DEPOSIT,
|
||||||
|
is_checkout=(random.random() > 0.2),
|
||||||
|
amount=Decimal(random.randint(1, 99)/10)
|
||||||
|
)
|
||||||
|
# 0.1 probability to have a withdrawal
|
||||||
|
elif typevar > 0.8 and account != liq_account:
|
||||||
|
ope = Operation(
|
||||||
|
group=opegroup,
|
||||||
|
type=Operation.WITHDRAW,
|
||||||
|
is_checkout=(random.random() > 0.2),
|
||||||
|
amount=-Decimal(random.randint(1, 99)/10)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
article = random.choice(articles)
|
||||||
|
nb = random.randint(1, 5)
|
||||||
|
|
||||||
|
ope = Operation(
|
||||||
|
group=opegroup,
|
||||||
|
type=Operation.PURCHASE,
|
||||||
|
amount=-article.price*nb,
|
||||||
|
article=article,
|
||||||
|
article_nb=nb
|
||||||
|
)
|
||||||
|
|
||||||
|
purchases += 1
|
||||||
|
|
||||||
|
if addcost:
|
||||||
|
ope.addcost_for = addcost_for
|
||||||
|
ope.addcost_amount = addcost_amount * nb
|
||||||
|
ope.amount -= ope.addcost_amount
|
||||||
|
|
||||||
|
ope_list.append(ope)
|
||||||
|
amount += ope.amount
|
||||||
|
|
||||||
|
Operation.objects.bulk_create(ope_list)
|
||||||
|
opes_created += len(ope_list)
|
||||||
|
opegroup.amount = amount
|
||||||
|
opegroup.save()
|
||||||
|
|
||||||
|
# Transfer generation
|
||||||
|
for i in range(num_transfers):
|
||||||
|
|
||||||
|
# Randomly pick time
|
||||||
|
at = timezone.now() - timedelta(
|
||||||
|
seconds=random.randint(0, time))
|
||||||
|
|
||||||
|
# Choose whether to have a comment
|
||||||
|
if random.random() > 0.5:
|
||||||
|
comment = "placeholder comment"
|
||||||
|
else:
|
||||||
|
comment = ""
|
||||||
|
|
||||||
|
transfergroup = TransferGroup.objects.create(
|
||||||
|
at=at,
|
||||||
|
comment=comment,
|
||||||
|
valid_by=random.choice(accounts)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Randomly generate transfer
|
||||||
|
transfer_list = []
|
||||||
|
for i in range(random.randint(1, 4)):
|
||||||
|
transfer_list.append(Transfer(
|
||||||
|
group=transfergroup,
|
||||||
|
from_acc=random.choice(accounts),
|
||||||
|
to_acc=random.choice(accounts),
|
||||||
|
amount=Decimal(random.randint(1, 99)/10)
|
||||||
|
))
|
||||||
|
|
||||||
|
Transfer.objects.bulk_create(transfer_list)
|
||||||
|
transfers += len(transfer_list)
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
"- {:d} opérations créées dont {:d} commandes d'articles"
|
||||||
|
.format(opes_created, purchases))
|
||||||
|
|
||||||
|
if transfers:
|
||||||
|
self.stdout.write("- {:d} transferts créés"
|
||||||
|
.format(transfers))
|
|
@ -5,15 +5,15 @@ Crée des utilisateurs, des articles et des opérations aléatoires
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.models import User, Group, Permission, ContentType
|
from django.contrib.auth.models import User, Group, Permission, ContentType
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
from gestioncof.management.base import MyBaseCommand
|
from gestioncof.management.base import MyBaseCommand
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
from kfet.models import (Account, Article, OperationGroup, Operation,
|
from kfet.models import (Account, Checkout, CheckoutStatement, Supplier,
|
||||||
Checkout, CheckoutStatement)
|
SupplierArticle, Article)
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
# Où sont stockés les fichiers json
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||||
|
@ -126,65 +126,24 @@ class Command(MyBaseCommand):
|
||||||
amount_error=0
|
amount_error=0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# Fournisseur
|
||||||
|
# ---
|
||||||
|
|
||||||
|
supplier, created = Supplier.objects.get_or_create(name="Panoramix")
|
||||||
|
if created:
|
||||||
|
articles = random.sample(list(Article.objects.all()), 40)
|
||||||
|
to_create = []
|
||||||
|
for article in articles:
|
||||||
|
to_create.append(SupplierArticle(
|
||||||
|
supplier=supplier,
|
||||||
|
article=article
|
||||||
|
))
|
||||||
|
|
||||||
|
SupplierArticle.objects.bulk_create(to_create)
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Opérations
|
# Opérations
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
self.stdout.write("Génération d'opérations")
|
call_command('createopes', '100', '7', '--transfers=20')
|
||||||
|
|
||||||
articles = Article.objects.all()
|
|
||||||
accounts = Account.objects.exclude(trigramme='LIQ')
|
|
||||||
|
|
||||||
num_op = 100
|
|
||||||
# Operations are put uniformly over the span of a week
|
|
||||||
past_date = 3600*24*7
|
|
||||||
|
|
||||||
for i in range(num_op):
|
|
||||||
if random.random() > 0.25:
|
|
||||||
account = random.choice(accounts)
|
|
||||||
else:
|
|
||||||
account = liq_account
|
|
||||||
|
|
||||||
amount = Decimal('0')
|
|
||||||
at = timezone.now() - timedelta(
|
|
||||||
seconds=random.randint(0, past_date))
|
|
||||||
|
|
||||||
opegroup = OperationGroup(
|
|
||||||
on_acc=account,
|
|
||||||
checkout=checkout,
|
|
||||||
at=at,
|
|
||||||
is_cof=account.cofprofile.is_cof)
|
|
||||||
|
|
||||||
opegroup.save()
|
|
||||||
|
|
||||||
for j in range(random.randint(1, 4)):
|
|
||||||
typevar = random.random()
|
|
||||||
if typevar > 0.9 and account != liq_account:
|
|
||||||
ope = Operation(
|
|
||||||
group=opegroup,
|
|
||||||
type=Operation.DEPOSIT,
|
|
||||||
amount=Decimal(random.randint(1, 99)/10,)
|
|
||||||
)
|
|
||||||
elif typevar > 0.8 and account != liq_account:
|
|
||||||
ope = Operation(
|
|
||||||
group=opegroup,
|
|
||||||
type=Operation.WITHDRAW,
|
|
||||||
amount=-Decimal(random.randint(1, 99)/10,)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
article = random.choice(articles)
|
|
||||||
nb = random.randint(1, 5)
|
|
||||||
|
|
||||||
ope = Operation(
|
|
||||||
group=opegroup,
|
|
||||||
type=Operation.PURCHASE,
|
|
||||||
amount=-article.price*nb,
|
|
||||||
article=article,
|
|
||||||
article_nb=nb
|
|
||||||
)
|
|
||||||
|
|
||||||
ope.save()
|
|
||||||
amount += ope.amount
|
|
||||||
|
|
||||||
opegroup.amount = amount
|
|
||||||
opegroup.save()
|
|
||||||
|
|
25
kfet/migrations/0048_default_datetime.py
Normal file
25
kfet/migrations/0048_default_datetime.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0047_auto_20170104_1528'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='operationgroup',
|
||||||
|
name='at',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transfergroup',
|
||||||
|
name='at',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
]
|
15
kfet/migrations/0049_merge.py
Normal file
15
kfet/migrations/0049_merge.py
Normal 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 = [
|
||||||
|
]
|
23
kfet/migrations/0050_remove_checkout.py
Normal file
23
kfet/migrations/0050_remove_checkout.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
138
kfet/models.py
138
kfet/models.py
|
@ -40,7 +40,7 @@ class Account(models.Model):
|
||||||
balance = models.DecimalField(
|
balance = models.DecimalField(
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
default = 0)
|
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)
|
created_at = models.DateTimeField(auto_now_add = True, null = True)
|
||||||
# Optional
|
# Optional
|
||||||
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
|
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
|
||||||
|
@ -48,6 +48,7 @@ class Account(models.Model):
|
||||||
choices = PROMO_CHOICES,
|
choices = PROMO_CHOICES,
|
||||||
blank = True, null = True, default = default_promo())
|
blank = True, null = True, default = default_promo())
|
||||||
nickname = models.CharField(
|
nickname = models.CharField(
|
||||||
|
"surnom(s)",
|
||||||
max_length = 255,
|
max_length = 255,
|
||||||
blank = True, default = "")
|
blank = True, default = "")
|
||||||
password = models.CharField(
|
password = models.CharField(
|
||||||
|
@ -224,14 +225,18 @@ class AccountNegative(models.Model):
|
||||||
start = models.DateTimeField(
|
start = models.DateTimeField(
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
balance_offset = models.DecimalField(
|
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,
|
max_digits = 6, decimal_places = 2,
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
authz_overdraft_amount = models.DecimalField(
|
authz_overdraft_amount = models.DecimalField(
|
||||||
|
"négatif autorisé",
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
authz_overdraft_until = models.DateTimeField(
|
authz_overdraft_until = models.DateTimeField(
|
||||||
|
"expiration du négatif",
|
||||||
blank = True, null = True, default = None)
|
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
|
@python_2_unicode_compatible
|
||||||
class Checkout(models.Model):
|
class Checkout(models.Model):
|
||||||
|
@ -273,29 +278,35 @@ class CheckoutStatement(models.Model):
|
||||||
checkout = models.ForeignKey(
|
checkout = models.ForeignKey(
|
||||||
Checkout, on_delete = models.PROTECT,
|
Checkout, on_delete = models.PROTECT,
|
||||||
related_name = "statements")
|
related_name = "statements")
|
||||||
balance_old = models.DecimalField(max_digits = 6, decimal_places = 2)
|
balance_old = models.DecimalField("ancienne balance",
|
||||||
balance_new = models.DecimalField(max_digits = 6, decimal_places = 2)
|
max_digits = 6, decimal_places = 2)
|
||||||
amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2)
|
balance_new = models.DecimalField("nouvelle balance",
|
||||||
amount_error = models.DecimalField(max_digits = 6, decimal_places = 2)
|
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)
|
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_001 = models.PositiveSmallIntegerField("pièces de 1¢", default=0)
|
||||||
taken_002 = models.PositiveSmallIntegerField(default=0)
|
taken_002 = models.PositiveSmallIntegerField("pièces de 2¢", default=0)
|
||||||
taken_005 = models.PositiveSmallIntegerField(default=0)
|
taken_005 = models.PositiveSmallIntegerField("pièces de 5¢", default=0)
|
||||||
taken_01 = models.PositiveSmallIntegerField(default=0)
|
taken_01 = models.PositiveSmallIntegerField("pièces de 10¢", default=0)
|
||||||
taken_02 = models.PositiveSmallIntegerField(default=0)
|
taken_02 = models.PositiveSmallIntegerField("pièces de 20¢", default=0)
|
||||||
taken_05 = models.PositiveSmallIntegerField(default=0)
|
taken_05 = models.PositiveSmallIntegerField("pièces de 50¢", default=0)
|
||||||
taken_1 = models.PositiveSmallIntegerField(default=0)
|
taken_1 = models.PositiveSmallIntegerField("pièces de 1€", default=0)
|
||||||
taken_2 = models.PositiveSmallIntegerField(default=0)
|
taken_2 = models.PositiveSmallIntegerField("pièces de 2€", default=0)
|
||||||
taken_5 = models.PositiveSmallIntegerField(default=0)
|
taken_5 = models.PositiveSmallIntegerField("billets de 5€", default=0)
|
||||||
taken_10 = models.PositiveSmallIntegerField(default=0)
|
taken_10 = models.PositiveSmallIntegerField("billets de 10€", default=0)
|
||||||
taken_20 = models.PositiveSmallIntegerField(default=0)
|
taken_20 = models.PositiveSmallIntegerField("billets de 20€", default=0)
|
||||||
taken_50 = models.PositiveSmallIntegerField(default=0)
|
taken_50 = models.PositiveSmallIntegerField("billets de 50€", default=0)
|
||||||
taken_100 = models.PositiveSmallIntegerField(default=0)
|
taken_100 = models.PositiveSmallIntegerField("billets de 100€", default=0)
|
||||||
taken_200 = models.PositiveSmallIntegerField(default=0)
|
taken_200 = models.PositiveSmallIntegerField("billets de 200€", default=0)
|
||||||
taken_500 = models.PositiveSmallIntegerField(default=0)
|
taken_500 = models.PositiveSmallIntegerField("billets de 500€", default=0)
|
||||||
taken_cheque = models.DecimalField(default=0, max_digits=6, decimal_places=2)
|
taken_cheque = models.DecimalField(
|
||||||
|
"montant des chèques",
|
||||||
|
default=0, max_digits=6, decimal_places=2)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s %s' % (self.checkout, self.at)
|
return '%s %s' % (self.checkout, self.at)
|
||||||
|
@ -336,19 +347,21 @@ class ArticleCategory(models.Model):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
name = models.CharField(max_length = 45)
|
name = models.CharField("nom", max_length = 45)
|
||||||
is_sold = models.BooleanField(default = True)
|
is_sold = models.BooleanField("en vente", default = True)
|
||||||
hidden = models.BooleanField(default=False,
|
hidden = models.BooleanField("caché",
|
||||||
|
default=False,
|
||||||
help_text="Si oui, ne sera pas affiché "
|
help_text="Si oui, ne sera pas affiché "
|
||||||
"au public ; par exemple "
|
"au public ; par exemple "
|
||||||
"sur la carte.")
|
"sur la carte.")
|
||||||
price = models.DecimalField(
|
price = models.DecimalField(
|
||||||
|
"prix",
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
default = 0)
|
default = 0)
|
||||||
stock = models.IntegerField(default = 0)
|
stock = models.IntegerField(default = 0)
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
ArticleCategory, on_delete = models.PROTECT,
|
ArticleCategory, on_delete = models.PROTECT,
|
||||||
related_name = "articles")
|
related_name = "articles", verbose_name='catégorie')
|
||||||
BOX_TYPE_CHOICES = (
|
BOX_TYPE_CHOICES = (
|
||||||
("caisse", "caisse"),
|
("caisse", "caisse"),
|
||||||
("carton", "carton"),
|
("carton", "carton"),
|
||||||
|
@ -356,10 +369,12 @@ class Article(models.Model):
|
||||||
("fût", "fût"),
|
("fût", "fût"),
|
||||||
)
|
)
|
||||||
box_type = models.CharField(
|
box_type = models.CharField(
|
||||||
|
"type de contenant",
|
||||||
choices = BOX_TYPE_CHOICES,
|
choices = BOX_TYPE_CHOICES,
|
||||||
max_length = choices_length(BOX_TYPE_CHOICES),
|
max_length = choices_length(BOX_TYPE_CHOICES),
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
box_capacity = models.PositiveSmallIntegerField(
|
box_capacity = models.PositiveSmallIntegerField(
|
||||||
|
"capacité du contenant",
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -417,11 +432,11 @@ class Supplier(models.Model):
|
||||||
Article,
|
Article,
|
||||||
through = 'SupplierArticle',
|
through = 'SupplierArticle',
|
||||||
related_name = "suppliers")
|
related_name = "suppliers")
|
||||||
name = models.CharField(max_length = 45)
|
name = models.CharField("nom", max_length = 45)
|
||||||
address = models.TextField()
|
address = models.TextField("adresse")
|
||||||
email = models.EmailField()
|
email = models.EmailField("adresse mail")
|
||||||
phone = models.CharField(max_length = 10)
|
phone = models.CharField("téléphone", max_length = 10)
|
||||||
comment = models.TextField()
|
comment = models.TextField("commentaire")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -466,7 +481,7 @@ class OrderArticle(models.Model):
|
||||||
quantity_received = models.IntegerField(default = 0)
|
quantity_received = models.IntegerField(default = 0)
|
||||||
|
|
||||||
class TransferGroup(models.Model):
|
class TransferGroup(models.Model):
|
||||||
at = models.DateTimeField(auto_now_add = True)
|
at = models.DateTimeField(default=timezone.now)
|
||||||
# Optional
|
# Optional
|
||||||
comment = models.CharField(
|
comment = models.CharField(
|
||||||
max_length = 255,
|
max_length = 255,
|
||||||
|
@ -502,7 +517,7 @@ class OperationGroup(models.Model):
|
||||||
checkout = models.ForeignKey(
|
checkout = models.ForeignKey(
|
||||||
Checkout, on_delete = models.PROTECT,
|
Checkout, on_delete = models.PROTECT,
|
||||||
related_name = "opesgroup")
|
related_name = "opesgroup")
|
||||||
at = models.DateTimeField(auto_now_add = True)
|
at = models.DateTimeField(default=timezone.now)
|
||||||
amount = models.DecimalField(
|
amount = models.DecimalField(
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
default = 0)
|
default = 0)
|
||||||
|
@ -525,51 +540,60 @@ class Operation(models.Model):
|
||||||
DEPOSIT = 'deposit'
|
DEPOSIT = 'deposit'
|
||||||
WITHDRAW = 'withdraw'
|
WITHDRAW = 'withdraw'
|
||||||
INITIAL = 'initial'
|
INITIAL = 'initial'
|
||||||
|
EDIT = 'edit'
|
||||||
|
|
||||||
TYPE_ORDER_CHOICES = (
|
TYPE_ORDER_CHOICES = (
|
||||||
(PURCHASE, 'Achat'),
|
(PURCHASE, 'Achat'),
|
||||||
(DEPOSIT, 'Charge'),
|
(DEPOSIT, 'Charge'),
|
||||||
(WITHDRAW, 'Retrait'),
|
(WITHDRAW, 'Retrait'),
|
||||||
(INITIAL, 'Initial'),
|
(INITIAL, 'Initial'),
|
||||||
|
(EDIT, 'Édition'),
|
||||||
)
|
)
|
||||||
|
|
||||||
group = models.ForeignKey(
|
group = models.ForeignKey(
|
||||||
OperationGroup, on_delete = models.PROTECT,
|
OperationGroup, on_delete=models.PROTECT,
|
||||||
related_name = "opes")
|
related_name="opes")
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
choices = TYPE_ORDER_CHOICES,
|
choices=TYPE_ORDER_CHOICES,
|
||||||
max_length = choices_length(TYPE_ORDER_CHOICES))
|
max_length=choices_length(TYPE_ORDER_CHOICES))
|
||||||
amount = models.DecimalField(
|
amount = models.DecimalField(
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits=6, decimal_places=2,
|
||||||
blank = True, default = 0)
|
blank=True, default=0)
|
||||||
is_checkout = models.BooleanField(default = True)
|
|
||||||
# Optional
|
# Optional
|
||||||
article = models.ForeignKey(
|
article = models.ForeignKey(
|
||||||
Article, on_delete = models.PROTECT,
|
Article, on_delete=models.PROTECT,
|
||||||
related_name = "operations",
|
related_name="operations",
|
||||||
blank = True, null = True, default = None)
|
blank=True, null=True, default=None)
|
||||||
article_nb = models.PositiveSmallIntegerField(
|
article_nb = models.PositiveSmallIntegerField(
|
||||||
blank = True, null = True, default = None)
|
blank=True, null=True, default=None)
|
||||||
canceled_by = models.ForeignKey(
|
canceled_by = models.ForeignKey(
|
||||||
Account, on_delete = models.PROTECT,
|
Account, on_delete=models.PROTECT,
|
||||||
related_name = "+",
|
related_name="+",
|
||||||
blank = True, null = True, default = None)
|
blank=True, null=True, default=None)
|
||||||
canceled_at = models.DateTimeField(
|
canceled_at = models.DateTimeField(
|
||||||
blank = True, null = True, default = None)
|
blank=True, null=True, default=None)
|
||||||
addcost_for = models.ForeignKey(
|
addcost_for = models.ForeignKey(
|
||||||
Account, on_delete = models.PROTECT,
|
Account, on_delete=models.PROTECT,
|
||||||
related_name = "addcosts",
|
related_name="addcosts",
|
||||||
blank = True, null = True, default = None)
|
blank=True, null=True, default=None)
|
||||||
addcost_amount = models.DecimalField(
|
addcost_amount = models.DecimalField(
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits=6, decimal_places=2,
|
||||||
blank = True, null = True, default = None)
|
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):
|
def __str__(self):
|
||||||
templates = {
|
templates = {
|
||||||
self.PURCHASE: "{nb} {article.name} ({amount}€)",
|
self.PURCHASE: "{nb} {article.name} ({amount}€)",
|
||||||
self.DEPOSIT: "charge ({amount})",
|
self.DEPOSIT: "charge ({amount}€)",
|
||||||
self.WITHDRAW: "retrait ({amount})",
|
self.WITHDRAW: "retrait ({amount}€)",
|
||||||
self.INITIAL: "initial ({amount})",
|
self.INITIAL: "initial ({amount}€)",
|
||||||
|
self.EDIT: "édition ({amount}€)",
|
||||||
}
|
}
|
||||||
return templates[self.type].format(nb=self.article_nb,
|
return templates[self.type].format(nb=self.article_nb,
|
||||||
article=self.article,
|
article=self.article,
|
||||||
|
|
|
@ -31,13 +31,22 @@ function KHistory(options={}) {
|
||||||
if (ope['type'] == 'purchase') {
|
if (ope['type'] == 'purchase') {
|
||||||
infos1 = ope['article_nb'];
|
infos1 = ope['article_nb'];
|
||||||
infos2 = ope['article__name'];
|
infos2 = ope['article__name'];
|
||||||
} else if (ope['type'] == 'initial') {
|
|
||||||
infos1 = parsed_amount.toFixed(2)+'€';
|
|
||||||
infos2 = 'Initial';
|
|
||||||
} else {
|
} else {
|
||||||
infos1 = parsed_amount.toFixed(2)+'€';
|
infos1 = parsed_amount.toFixed(2)+'€';
|
||||||
infos2 = (ope['type'] == 'deposit') ? 'Charge' : 'Retrait';
|
switch (ope['type']) {
|
||||||
infos2 = ope['is_checkout'] ? infos2 : 'Édition';
|
case 'initial':
|
||||||
|
infos2 = 'Initial';
|
||||||
|
break;
|
||||||
|
case 'withdraw':
|
||||||
|
infos2 = 'Retrait';
|
||||||
|
break;
|
||||||
|
case 'deposit':
|
||||||
|
infos2 = 'Charge';
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
infos2 = 'Édition';
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ope_html
|
$ope_html
|
||||||
|
|
|
@ -27,6 +27,53 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generic Websocket class and k-psul ws instanciation
|
||||||
|
*/
|
||||||
|
|
||||||
|
class KfetWebsocket {
|
||||||
|
|
||||||
|
static get defaults() {
|
||||||
|
return {"relative_url": "", "default_msg": {}, "handlers": []};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
$.extend(this, this.constructor.defaults, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
||||||
|
var location_host = window.location.host;
|
||||||
|
var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host;
|
||||||
|
|
||||||
|
return websocket_protocol+"://" + location_url + this.relative_url ;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_handler(handler) {
|
||||||
|
if (!this.socket)
|
||||||
|
this.listen();
|
||||||
|
|
||||||
|
this.handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
listen() {
|
||||||
|
var that = this;
|
||||||
|
this.socket = new ReconnectingWebSocket(this.url);
|
||||||
|
|
||||||
|
this.socket.onmessage = function(e) {
|
||||||
|
var data = $.extend({}, that.default_msg, JSON.parse(e.data));
|
||||||
|
for (let handler of that.handlers) {
|
||||||
|
handler(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var OperationWebSocket = new KfetWebsocket({
|
||||||
|
'relative_url': '/ws/k-fet/k-psul/',
|
||||||
|
'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]},
|
||||||
|
});
|
||||||
|
|
||||||
function dateUTCToParis(date) {
|
function dateUTCToParis(date) {
|
||||||
return moment.tz(date, 'UTC').tz('Europe/Paris');
|
return moment.tz(date, 'UTC').tz('Europe/Paris');
|
||||||
}
|
}
|
||||||
|
@ -86,6 +133,12 @@ function getErrorsHtml(data) {
|
||||||
content += '<li>Montant invalide</li>';
|
content += '<li>Montant invalide</li>';
|
||||||
content += '</ul>';
|
content += '</ul>';
|
||||||
}
|
}
|
||||||
|
if ('account' in data['errors']) {
|
||||||
|
content += 'Général';
|
||||||
|
content += '<ul>';
|
||||||
|
content += '<li>Opération invalide sur le compte '+data['errors']['account']+'</li>';
|
||||||
|
content += '</ul>';
|
||||||
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block title %}Nouvel article{% endblock %}
|
{% block title %}Nouvel article{% endblock %}
|
||||||
{% block content-header-title %}Création d'un article{% endblock %}
|
{% block content-header-title %}Création d'un article{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form submit="" method="post">
|
{% 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 %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{% include 'kfet/form_snippet.html' with form=form %}
|
||||||
{% if not perms.kfet.add_article %}
|
{% if not perms.kfet.add_article %}
|
||||||
<input type="password" name="KFETPASSWORD">
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Enregistrer">
|
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -101,9 +101,8 @@
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
jQuery(document).ready(function() {
|
jQuery(document).ready(function() {
|
||||||
var stat_last = $("#stat_last");
|
var stat_last = new StatsGroup("{% url 'kfet.article.stat.last' article.id %}",
|
||||||
var stat_last_url = "{% url 'kfet.article.stat.last' article.id %}";
|
$("#stat_last"));
|
||||||
STAT.get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block title %}Édition de l'article {{ article.name }}{% endblock %}
|
{% block title %}Édition de l'article {{ article.name }}{% endblock %}
|
||||||
{% block content-header-title %}Article {{ article.name }} - Édition{% endblock %}
|
{% block content-header-title %}Article {{ article.name }} - Édition{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form action="" method="post">
|
{% 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 %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{% include 'kfet/form_snippet.html' with form=form %}
|
||||||
{% if not perms.kfet.change_article %}
|
{% if not perms.kfet.change_article %}
|
||||||
<input type="password" name="KFETPASSWORD">
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Mettre à jour">
|
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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>
|
<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>
|
</tr>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
<div class="content-right-block">
|
<div class="content-right-block">
|
||||||
|
|
|
@ -15,15 +15,15 @@ Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-8 col-md-9 col-content-right">
|
<div class="col-sm-8 col-md-9 col-content-right">
|
||||||
{% include 'kfet/base_messages.html' %}
|
{% include 'kfet/base_messages.html' %}
|
||||||
<div class="content-right">
|
<div class="content-right form-only">
|
||||||
<div class="content-right-block">
|
<div class="content-form">
|
||||||
<form action="" method="post">
|
<form submit="" method="post" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{% include 'kfet/form_snippet.html' with form=form %}
|
||||||
{% if not perms.kfet.change_checkoutstatement %}
|
{% if not perms.kfet.change_checkoutstatement %}
|
||||||
<input type="password" name="KFETPASSWORD">
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Enregistrer">
|
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
{{ field|add_class:'form-control' }}
|
{{ 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -876,15 +875,27 @@ $(document).ready(function() {
|
||||||
return (-5 <= stock - nb && stock - nb <= 5);
|
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 deposit_basket_html = $(item_basket_default_html);
|
||||||
var amount = parseFloat(amount).toFixed(2);
|
var amount = parseFloat(amount).toFixed(2);
|
||||||
var index = addDepositToFormset(amount, is_checkout);
|
var index = addDepositToFormset(amount);
|
||||||
var text = is_checkout ? 'Charge' : 'Édition';
|
|
||||||
deposit_basket_html
|
deposit_basket_html
|
||||||
.attr('data-opeindex', index)
|
.attr('data-opeindex', index)
|
||||||
.find('.number').text(amount+"€").end()
|
.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));
|
.find('.amount').text(amountToUKF(amount, account_data['is_cof'], false));
|
||||||
basket_container.prepend(deposit_basket_html);
|
basket_container.prepend(deposit_basket_html);
|
||||||
updateBasketRel();
|
updateBasketRel();
|
||||||
|
@ -1047,11 +1058,10 @@ $(document).ready(function() {
|
||||||
// Ask deposit or withdraw
|
// Ask deposit or withdraw
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
function askDeposit(is_checkout=1) {
|
function askDeposit() {
|
||||||
var title = is_checkout ? 'Montant de la charge' : "Montant de l'édition";
|
|
||||||
$.confirm({
|
$.confirm({
|
||||||
title: title,
|
title: 'Montant de la charge',
|
||||||
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,
|
backgroundDismiss: true,
|
||||||
animation:'top',
|
animation:'top',
|
||||||
closeAnimation:'bottom',
|
closeAnimation:'bottom',
|
||||||
|
@ -1060,7 +1070,34 @@ $(document).ready(function() {
|
||||||
var amount = this.$content.find('input').val();
|
var amount = this.$content.find('input').val();
|
||||||
if (!$.isNumeric(amount) || amount <= 0)
|
if (!$.isNumeric(amount) || amount <= 0)
|
||||||
return false;
|
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() {
|
onOpen: function() {
|
||||||
var that = this
|
var that = this
|
||||||
|
@ -1078,7 +1115,7 @@ $(document).ready(function() {
|
||||||
function askWithdraw() {
|
function askWithdraw() {
|
||||||
$.confirm({
|
$.confirm({
|
||||||
title: 'Montant du retrait',
|
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,
|
backgroundDismiss: true,
|
||||||
animation:'top',
|
animation:'top',
|
||||||
closeAnimation:'bottom',
|
closeAnimation:'bottom',
|
||||||
|
@ -1125,7 +1162,7 @@ $(document).ready(function() {
|
||||||
var mngmt_total_forms = 1;
|
var mngmt_total_forms = 1;
|
||||||
var prefix_regex = /__prefix__/;
|
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 operation_html = operation_empty_html.clone();
|
||||||
var index = mngmt_total_forms;
|
var index = mngmt_total_forms;
|
||||||
|
|
||||||
|
@ -1135,8 +1172,7 @@ $(document).ready(function() {
|
||||||
.find('#id_form-__prefix__-type').val(type).end()
|
.find('#id_form-__prefix__-type').val(type).end()
|
||||||
.find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).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').val(article).end()
|
||||||
.find('#id_form-__prefix__-article_nb').val(article_nb).end()
|
.find('#id_form-__prefix__-article_nb').val(article_nb).end();
|
||||||
.find('#id_form-__prefix__-is_checkout').val(is_checkout);
|
|
||||||
|
|
||||||
mngmt_total_forms_input.val(index+1);
|
mngmt_total_forms_input.val(index+1);
|
||||||
mngmt_total_forms++;
|
mngmt_total_forms++;
|
||||||
|
@ -1151,12 +1187,16 @@ $(document).ready(function() {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDepositToFormset(amount, is_checkout=1) {
|
function addDepositToFormset(amount) {
|
||||||
return addOperationToFormset('deposit', amount, '', '', is_checkout);
|
return addOperationToFormset('deposit', amount, '', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWithdrawToFormset(amount, is_checkout=1) {
|
function addEditToFormset(amount) {
|
||||||
return addOperationToFormset('withdraw', amount, '', '', is_checkout);
|
return addOperationToFormset('edit', amount, '', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWithdrawToFormset(amount) {
|
||||||
|
return addOperationToFormset('withdraw', amount, '', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPurchaseToFormset(article_id, article_nb, amount=0) {
|
function addPurchaseToFormset(article_id, article_nb, amount=0) {
|
||||||
|
@ -1321,15 +1361,7 @@ $(document).ready(function() {
|
||||||
// Synchronization
|
// Synchronization
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}
|
OperationWebSocket.add_handler(function(data) {
|
||||||
|
|
||||||
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
|
||||||
var location_host = window.location.host;
|
|
||||||
var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host;
|
|
||||||
socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + "/ws/k-fet/k-psul/");
|
|
||||||
socket.onmessage = function(e) {
|
|
||||||
data = $.extend({}, websocket_msg_default, JSON.parse(e.data));
|
|
||||||
|
|
||||||
for (var i=0; i<data['opegroups'].length; i++) {
|
for (var i=0; i<data['opegroups'].length; i++) {
|
||||||
if (data['opegroups'][i]['add']) {
|
if (data['opegroups'][i]['add']) {
|
||||||
khistory.addOpeGroup(data['opegroups'][i]);
|
khistory.addOpeGroup(data['opegroups'][i]);
|
||||||
|
@ -1349,18 +1381,26 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var i=0; i<data['articles'].length; i++) {
|
for (var i=0; i<data['articles'].length; i++) {
|
||||||
article = data['articles'][i];
|
var article = data['articles'][i];
|
||||||
articles_container.find('#data-article-'+article['id'])
|
var article_line = articles_container.find('#data-article-'+article.id);
|
||||||
.addClass('low-stock');
|
if (article.stock <= 5 && article.stock >= -5) {
|
||||||
articles_container.find('#data-article-'+article['id']+' .stock')
|
article_line.addClass('low-stock');
|
||||||
|
} else {
|
||||||
|
article_line.removeClass('low-stock');
|
||||||
|
}
|
||||||
|
article_line.find('.stock')
|
||||||
.text(article['stock']);
|
.text(article['stock']);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < articlesList.length && articlesList[i][1] != article.id) i++ ;
|
||||||
|
articlesList[i][4] = article.stock ;
|
||||||
}
|
}
|
||||||
if (data['addcost']) {
|
if (data['addcost']) {
|
||||||
settings['addcost_for'] = data['addcost']['for'];
|
settings['addcost_for'] = data['addcost']['for'];
|
||||||
settings['addcost_amount'] = parseFloat(data['addcost']['amount']);
|
settings['addcost_amount'] = parseFloat(data['addcost']['amount']);
|
||||||
displayAddcost();
|
displayAddcost();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// -----
|
// -----
|
||||||
// General
|
// General
|
||||||
|
@ -1441,7 +1481,7 @@ $(document).ready(function() {
|
||||||
return false;
|
return false;
|
||||||
case 119:
|
case 119:
|
||||||
// F8 - Edition
|
// F8 - Edition
|
||||||
askDeposit(0);
|
askEdit();
|
||||||
return false;
|
return false;
|
||||||
case 120:
|
case 120:
|
||||||
// F9 - Addcost
|
// F9 - Addcost
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}Nouvelle commande{% endblock %}
|
{% block title %}Nouvelle commande{% endblock %}
|
||||||
{% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %}
|
{% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %}
|
||||||
|
@ -60,7 +61,7 @@
|
||||||
<td>{{ form.v_prev }}</td>
|
<td>{{ form.v_prev }}</td>
|
||||||
<td>{{ form.stock }}</td>
|
<td>{{ form.stock }}</td>
|
||||||
<td>{{ form.c_rec }}</td>
|
<td>{{ form.c_rec }}</td>
|
||||||
<td>{{ form.quantity_ordered }}</td>
|
<td>{{ form.quantity_ordered | add_class:"form-control" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}{% endblock %}
|
{% block title %}{% endblock %}
|
||||||
{% block content-header-title %}{% endblock %}
|
{% block content-header-title %}{% endblock %}
|
||||||
|
@ -33,11 +34,11 @@
|
||||||
<tr>
|
<tr>
|
||||||
{{ form.article }}
|
{{ form.article }}
|
||||||
<td>{{ form.name }}</td>
|
<td>{{ form.name }}</td>
|
||||||
<td>{{ form.price_HT }}</td>
|
<td>{{ form.price_HT | add_class:"form-control" }}</td>
|
||||||
<td>{{ form.TVA }}</td>
|
<td>{{ form.TVA | add_class:"form-control" }}</td>
|
||||||
<td>{{ form.rights }}</td>
|
<td>{{ form.rights | add_class:"form-control" }}</td>
|
||||||
<td>{{ form.quantity_ordered }}</td>
|
<td>{{ form.quantity_ordered }}</td>
|
||||||
<td>{{ form.quantity_received }}</td>
|
<td>{{ form.quantity_received | add_class:"form-control" }}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block title %}Fournisseur - Modification{% endblock %}
|
{% block title %}Fournisseur - Modification{% endblock %}
|
||||||
{% block content-header-title %}Fournisseur - Modification{% endblock %}
|
{% block content-header-title %}Fournisseur - Modification{% endblock %}
|
||||||
|
@ -7,13 +9,19 @@
|
||||||
|
|
||||||
{% include 'kfet/base_messages.html' %}
|
{% include 'kfet/base_messages.html' %}
|
||||||
|
|
||||||
<form action="" method="post">
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{% include 'kfet/form_snippet.html' with form=form %}
|
||||||
{% if not perms.kfet.change_supplier %}
|
{% if not perms.kfet.change_supplier %}
|
||||||
<input type="password" name="KFETPASSWORD">
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" class="btn btn-primary btn-lg">
|
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -165,8 +165,7 @@ def account_create_special(request):
|
||||||
ope = Operation.objects.create(
|
ope = Operation.objects.create(
|
||||||
group = opegroup,
|
group = opegroup,
|
||||||
type = Operation.INITIAL,
|
type = Operation.INITIAL,
|
||||||
amount = amount,
|
amount = amount)
|
||||||
is_checkout = False)
|
|
||||||
messages.success(request, 'Compte créé : %s' % account.trigramme)
|
messages.success(request, 'Compte créé : %s' % account.trigramme)
|
||||||
return redirect('kfet.account.create')
|
return redirect('kfet.account.create')
|
||||||
except Account.UserHasAccount as e:
|
except Account.UserHasAccount as e:
|
||||||
|
@ -1010,10 +1009,7 @@ def kpsul_perform_operations(request):
|
||||||
operation.amount -= operation.addcost_amount
|
operation.amount -= operation.addcost_amount
|
||||||
to_addcost_for_balance += operation.addcost_amount
|
to_addcost_for_balance += operation.addcost_amount
|
||||||
if operationgroup.on_acc.is_cash:
|
if operationgroup.on_acc.is_cash:
|
||||||
operation.is_checkout = True
|
|
||||||
to_checkout_balance += -operation.amount
|
to_checkout_balance += -operation.amount
|
||||||
else:
|
|
||||||
operation.is_checkout = False
|
|
||||||
if operationgroup.on_acc.is_cof:
|
if operationgroup.on_acc.is_cof:
|
||||||
if is_addcost:
|
if is_addcost:
|
||||||
operation.addcost_amount = operation.addcost_amount / cof_grant_divisor
|
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
|
to_articles_stocks[operation.article] -= operation.article_nb
|
||||||
else:
|
else:
|
||||||
if operationgroup.on_acc.is_cash:
|
if operationgroup.on_acc.is_cash:
|
||||||
data['errors']['account'] = 'Charge et retrait impossible sur LIQ'
|
data['errors']['account'] = 'LIQ'
|
||||||
|
if operation.type != Operation.EDIT:
|
||||||
to_checkout_balance += operation.amount
|
to_checkout_balance += operation.amount
|
||||||
operationgroup.amount += operation.amount
|
operationgroup.amount += operation.amount
|
||||||
if operation.type == Operation.DEPOSIT:
|
if operation.type == Operation.DEPOSIT:
|
||||||
required_perms.add('kfet.perform_deposit')
|
required_perms.add('kfet.perform_deposit')
|
||||||
if (not operation.is_checkout
|
if operation.type == Operation.EDIT:
|
||||||
and operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]):
|
|
||||||
required_perms.add('kfet.edit_balance_account')
|
required_perms.add('kfet.edit_balance_account')
|
||||||
need_comment = True
|
need_comment = True
|
||||||
if operationgroup.on_acc.is_cof:
|
if operationgroup.on_acc.is_cof:
|
||||||
|
@ -1041,6 +1037,8 @@ def kpsul_perform_operations(request):
|
||||||
operationgroup.comment = operationgroup.comment.strip()
|
operationgroup.comment = operationgroup.comment.strip()
|
||||||
if not operationgroup.comment:
|
if not operationgroup.comment:
|
||||||
data['errors']['need_comment'] = True
|
data['errors']['need_comment'] = True
|
||||||
|
|
||||||
|
if data['errors']:
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
if stop or not request.user.has_perms(required_perms):
|
if stop or not request.user.has_perms(required_perms):
|
||||||
|
@ -1122,8 +1120,7 @@ def kpsul_perform_operations(request):
|
||||||
ope_data = {
|
ope_data = {
|
||||||
'id': operation.pk, 'type': operation.type, 'amount': operation.amount,
|
'id': operation.pk, 'type': operation.type, 'amount': operation.amount,
|
||||||
'addcost_amount': operation.addcost_amount,
|
'addcost_amount': operation.addcost_amount,
|
||||||
'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None,
|
'addcost_for__trigramme': operation.addcost_for and addcost_for.trigramme or None,
|
||||||
'is_checkout': operation.is_checkout,
|
|
||||||
'article__name': operation.article and operation.article.name or None,
|
'article__name': operation.article and operation.article.name or None,
|
||||||
'article_nb': operation.article_nb,
|
'article_nb': operation.article_nb,
|
||||||
'group_id': operationgroup.pk,
|
'group_id': operationgroup.pk,
|
||||||
|
@ -1213,7 +1210,7 @@ def kpsul_cancel_operations(request):
|
||||||
.order_by('at')
|
.order_by('at')
|
||||||
.last())
|
.last())
|
||||||
if not last_statement or last_statement.at < ope.group.at:
|
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:
|
if ope.group.on_acc.is_cash:
|
||||||
to_checkouts_balances[ope.group.checkout] -= - ope.amount
|
to_checkouts_balances[ope.group.checkout] -= - ope.amount
|
||||||
else:
|
else:
|
||||||
|
@ -1378,7 +1375,6 @@ def history_json(request):
|
||||||
'type' : ope.type,
|
'type' : ope.type,
|
||||||
'amount' : ope.amount,
|
'amount' : ope.amount,
|
||||||
'article_nb' : ope.article_nb,
|
'article_nb' : ope.article_nb,
|
||||||
'is_checkout' : ope.is_checkout,
|
|
||||||
'addcost_amount': ope.addcost_amount,
|
'addcost_amount': ope.addcost_amount,
|
||||||
'canceled_at' : ope.canceled_at,
|
'canceled_at' : ope.canceled_at,
|
||||||
'article__name':
|
'article__name':
|
||||||
|
|
Loading…
Reference in a new issue