Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF

This commit is contained in:
Martin Pépin 2016-09-05 20:11:37 +02:00
commit bb6c3fdd20
140 changed files with 17827 additions and 2 deletions

3
cof/routing.py Normal file
View file

@ -0,0 +1,3 @@
from kfet.routing import channel_routing as kfet_channel_routings
channel_routing = kfet_channel_routings

View file

@ -50,6 +50,9 @@ INSTALLED_APPS = (
'django_cas_ng', 'django_cas_ng',
'debug_toolbar', 'debug_toolbar',
'bootstrapform', 'bootstrapform',
'kfet',
'channels',
'widget_tweaks',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -58,6 +61,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'kfet.middleware.KFetAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
@ -80,6 +84,7 @@ TEMPLATES = [
'django.core.context_processors.media', 'django.core.context_processors.media',
'django.core.context_processors.static', 'django.core.context_processors.static',
'gestioncof.shared.context_processor', 'gestioncof.shared.context_processor',
'kfet.context_processors.auth',
], ],
}, },
}, },
@ -87,7 +92,6 @@ TEMPLATES = [
# WSGI_APPLICATION = 'cof.wsgi.application' # WSGI_APPLICATION = 'cof.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
@ -120,6 +124,10 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static/'),
)
# Media upload (through ImageField, SiteField) # Media upload (through ImageField, SiteField)
# https://docs.djangoproject.com/en/1.9/ref/models/fields/ # https://docs.djangoproject.com/en/1.9/ref/models/fields/
@ -161,6 +169,17 @@ RECAPTCHA_PUBLIC_KEY = "DUMMY"
RECAPTCHA_PRIVATE_KEY = "DUMMY" RECAPTCHA_PRIVATE_KEY = "DUMMY"
RECAPTCHA_USE_SSL = True RECAPTCHA_USE_SSL = True
# Channels settings
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis://:password_redis@127.0.0.1:6379/0")],
},
"ROUTING": "cof.routing.channel_routing",
}
}
def show_toolbar(request): def show_toolbar(request):
""" """

View file

@ -81,6 +81,7 @@ urlpatterns = [
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff), url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof), url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente), url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
url(r'^k-fet/', include('kfet.urls'))
] + \ ] + \
(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) (static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG if settings.DEBUG

0
kfet/__init__.py Normal file
View file

3
kfet/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

80
kfet/autocomplete.py Normal file
View file

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.shortcuts import render
from django.http import Http404
from django.db.models import Q
from gestioncof.models import User, Clipper
from kfet.models import Account
def account_create(request):
if "q" not in request.GET:
raise Http404
q = request.GET.get("q")
if (len(q) == 0):
return render(request, "kfet/account_create_autocomplete.html")
data = {'q': q}
queries = {}
search_words = q.split()
queries['kfet'] = Account.objects
queries['users_cof'] = User.objects.filter(Q(profile__is_cof = True))
queries['users_notcof'] = User.objects.filter(Q(profile__is_cof = False))
queries['clippers'] = Clipper.objects
for word in search_words:
queries['kfet'] = queries['kfet'].filter(
Q(cofprofile__user__username__icontains = word)
| Q(cofprofile__user__first_name__icontains = word)
| Q(cofprofile__user__last_name__icontains = word)
)
queries['users_cof'] = queries['users_cof'].filter(
Q(username__icontains = word)
| Q(first_name__icontains = word)
| Q(last_name__icontains = word)
)
queries['users_notcof'] = queries['users_notcof'].filter(
Q(username__icontains = word)
| Q(first_name__icontains = word)
| Q(last_name__icontains = word)
)
queries['clippers'] = queries['clippers'].filter(
Q(username__icontains = word)
| Q(fullname__icontains = word)
)
queries['kfet'] = queries['kfet'].distinct()
usernames = list( \
queries['kfet'].values_list('cofprofile__user__username', flat=True))
queries['kfet'] = [ (account, account.cofprofile.user) \
for account in queries['kfet'] ]
queries['users_cof'] = \
queries['users_cof'].exclude(username__in=usernames).distinct()
queries['users_notcof'] = \
queries['users_notcof'].exclude(username__in=usernames).distinct()
usernames += list( \
queries['users_cof'].values_list('username', flat=True))
usernames += list( \
queries['users_notcof'].values_list('username', flat=True))
queries['clippers'] = \
queries['clippers'].exclude(username__in=usernames).distinct()
data.update(queries)
options = 0
for query in queries.values():
options += len(query)
data['options'] = options
return render(request, "kfet/account_create_autocomplete.html", data)

43
kfet/backends.py Normal file
View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
import hashlib
from django.contrib.auth.models import User, Permission
from gestioncof.models import CofProfile
from kfet.models import Account, GenericTeamToken
class KFetBackend(object):
def authenticate(self, request):
password = request.POST.get('KFETPASSWORD', '')
password = request.META.get('HTTP_KFETPASSWORD', password)
if not password:
return None
try:
password_sha256 = hashlib.sha256(password.encode()).hexdigest()
account = Account.objects.get(password=password_sha256)
user = account.cofprofile.user
except Account.DoesNotExist:
return None
return user
class GenericTeamBackend(object):
def authenticate(self, username=None, token=None):
valid_token = GenericTeamToken.objects.get(token=token)
if username == 'kfet_genericteam' and valid_token:
user, created = User.objects.get_or_create(username='kfet_genericteam')
perm_is_team = Permission.objects.get(codename='is_team')
user.user_permissions.add(perm_is_team)
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None

26
kfet/consumers.py Normal file
View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from channels import Group
from channels.generic.websockets import JsonWebsocketConsumer
class KPsul(JsonWebsocketConsumer):
# Set to True if you want them, else leave out
strict_ordering = False
slight_ordering = False
def connection_groups(self, **kwargs):
return ['kfet.kpsul']
def connect(self, message, **kwargs):
pass
def receive(self, content, **kwargs):
pass
def disconnect(self, message, **kwargs):
pass

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.contrib.auth.context_processors import PermWrapper
def auth(request):
if hasattr(request, 'real_user'):
return {
'user': request.real_user,
'perms': PermWrapper(request.real_user),
}
return {}

12
kfet/decorators.py Normal file
View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django_cas_ng.decorators import user_passes_test
def kfet_is_team(user):
return user.has_perm('kfet.is_team')
teamkfet_required = user_passes_test(lambda u: kfet_is_team(u))

516
kfet/forms.py Normal file
View file

@ -0,0 +1,516 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from decimal import Decimal
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.forms import modelformset_factory, inlineformset_factory
from django.forms.models import BaseInlineFormSet
from django.utils import timezone
from kfet.models import (Account, Checkout, Article, OperationGroup, Operation,
CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer,
TransferGroup, Supplier, Inventory, InventoryArticle)
from gestioncof.models import CofProfile
# -----
# Widgets
# -----
class DateTimeWidget(forms.DateTimeInput):
def __init__(self, attrs = None):
super(DateTimeWidget, self).__init__(attrs)
self.attrs['format'] = '%Y-%m-%d %H:%M'
class Media:
css = {
'all': ('kfet/css/bootstrap-datetimepicker.min.css',)
}
js = (
'kfet/js/moment.js',
'kfet/js/moment-fr.js',
'kfet/js/bootstrap-datetimepicker.min.js',
)
# -----
# Account forms
# -----
class AccountForm(forms.ModelForm):
# Surcharge pour passer data à Account.save()
def save(self, data = {}, *args, **kwargs):
obj = super(AccountForm, self).save(commit = False, *args, **kwargs)
obj.save(data = data)
return obj
class Meta:
model = Account
fields = ['trigramme', 'promo', 'nickname', 'is_frozen']
widgets = {
'trigramme': forms.TextInput(attrs={'autocomplete': 'off'}),
}
class AccountBalanceForm(forms.ModelForm):
class Meta:
model = Account
fields = ['balance']
class AccountTriForm(AccountForm):
def clean_trigramme(self):
trigramme = self.cleaned_data['trigramme']
return trigramme.upper()
class Meta(AccountForm.Meta):
fields = ['trigramme']
class AccountNoTriForm(AccountForm):
class Meta(AccountForm.Meta):
exclude = ['trigramme']
class AccountRestrictForm(AccountForm):
class Meta(AccountForm.Meta):
fields = ['is_frozen']
class AccountPwdForm(forms.Form):
pwd1 = forms.CharField(
widget=forms.PasswordInput)
pwd2 = forms.CharField(
widget=forms.PasswordInput)
def clean(self):
pwd1 = self.cleaned_data.get('pwd1', '')
pwd2 = self.cleaned_data.get('pwd2', '')
if len(pwd1) < 8:
raise ValidationError("Mot de passe trop court")
if pwd1 != pwd2:
raise ValidationError("Les mots de passes sont différents")
super(AccountPwdForm, self).clean()
class CofForm(forms.ModelForm):
def clean_is_cof(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.is_cof
else:
return False
class Meta:
model = CofProfile
fields = ['login_clipper', 'is_cof', 'departement']
class CofRestrictForm(CofForm):
class Meta(CofForm.Meta):
fields = ['departement']
class UserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
from_clipper = kwargs.pop('from_clipper', False)
new_user = kwargs.get('instance') is None and not from_clipper
super(UserForm, self).__init__(*args, **kwargs)
if new_user:
self.fields['username'].validators = [MinLengthValidator(9)]
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email']
help_texts = {
'username': ''
}
class UserRestrictForm(UserForm):
class Meta(UserForm.Meta):
fields = ['first_name', 'last_name']
class UserRestrictTeamForm(UserForm):
class Meta(UserForm.Meta):
fields = ['first_name', 'last_name', 'email']
class UserGroupForm(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
Group.objects.filter(name__icontains='K-Fêt'))
class Meta:
model = User
fields = ['groups']
class GroupForm(forms.ModelForm):
permissions = forms.ModelMultipleChoiceField(
queryset= Permission.objects.filter(content_type__in=
ContentType.objects.filter(app_label='kfet')))
def clean_name(self):
name = self.cleaned_data['name']
return 'K-Fêt %s' % name
class Meta:
model = Group
fields = ['name', 'permissions']
class AccountNegativeForm(forms.ModelForm):
class Meta:
model = AccountNegative
fields = ['authz_overdraft_amount', 'authz_overdraft_until',
'balance_offset', 'comment']
widgets = {
'authz_overdraft_until': DateTimeWidget(),
}
# -----
# Checkout forms
# -----
class CheckoutForm(forms.ModelForm):
class Meta:
model = Checkout
fields = ['name', 'valid_from', 'valid_to', 'balance', 'is_protected']
widgets = {
'valid_from': DateTimeWidget(),
'valid_to' : DateTimeWidget(),
}
class CheckoutRestrictForm(CheckoutForm):
class Meta(CheckoutForm.Meta):
fields = ['name', 'valid_from', 'valid_to']
class CheckoutStatementCreateForm(forms.ModelForm):
balance_001 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_002 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_005 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_01 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_02 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_05 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_1 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_2 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_5 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_10 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_20 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_50 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_100 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_200 = forms.IntegerField(min_value=0, initial=0, required=False)
balance_500 = forms.IntegerField(min_value=0, initial=0, required=False)
class Meta:
model = CheckoutStatement
exclude = ['by', 'at', 'checkout', 'amount_error', 'amount_taken',
'balance_old', 'balance_new']
def clean(self):
not_count = self.cleaned_data['not_count']
if not not_count and (
self.cleaned_data['balance_001'] is None
or self.cleaned_data['balance_002'] is None
or self.cleaned_data['balance_005'] is None
or self.cleaned_data['balance_01'] is None
or self.cleaned_data['balance_02'] is None
or self.cleaned_data['balance_05'] is None
or self.cleaned_data['balance_1'] is None
or self.cleaned_data['balance_2'] is None
or self.cleaned_data['balance_5'] is None
or self.cleaned_data['balance_10'] is None
or self.cleaned_data['balance_20'] is None
or self.cleaned_data['balance_50'] is None
or self.cleaned_data['balance_100'] is None
or self.cleaned_data['balance_200'] is None
or self.cleaned_data['balance_500'] is None):
raise ValidationError("Y'a un problème. Si tu comptes la caisse, mets au moins des 0 stp (et t'as pas idée de comment c'est long de vérifier que t'as mis des valeurs de partout...)")
super(CheckoutStatementCreateForm, self).clean()
class CheckoutStatementUpdateForm(forms.ModelForm):
class Meta:
model = CheckoutStatement
exclude = ['by', 'at', 'checkout', 'amount_error', 'amount_taken']
# -----
# Article forms
# -----
class ArticleForm(forms.ModelForm):
category_new = forms.CharField(
max_length=45,
required = False)
category = forms.ModelChoiceField(
queryset = ArticleCategory.objects.all(),
required = False)
suppliers = forms.ModelMultipleChoiceField(
queryset = Supplier.objects.all(),
required = False)
supplier_new = forms.CharField(
max_length = 45,
required = False)
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.initial['suppliers'] = self.instance.suppliers.values_list('pk', flat=True)
def clean(self):
category = self.cleaned_data.get('category')
category_new = self.cleaned_data.get('category_new')
if not category and not category_new:
raise ValidationError('Sélectionnez une catégorie ou créez en une')
elif not category:
category, _ = ArticleCategory.objects.get_or_create(name=category_new)
self.cleaned_data['category'] = category
super(ArticleForm, self).clean()
class Meta:
model = Article
fields = ['name', 'is_sold', 'price', 'stock', 'category', 'box_type',
'box_capacity']
class ArticleRestrictForm(ArticleForm):
class Meta(ArticleForm.Meta):
fields = ['name', 'is_sold', 'price', 'category', 'box_type',
'box_capacity']
# -----
# K-Psul forms
# -----
class KPsulOperationGroupForm(forms.ModelForm):
checkout = forms.ModelChoiceField(
queryset = Checkout.objects.filter(
is_protected=False, valid_from__lte=timezone.now(),
valid_to__gte=timezone.now()),
widget = forms.HiddenInput())
class Meta:
model = OperationGroup
fields = ['on_acc', 'checkout', 'comment']
widgets = {
'on_acc' : forms.HiddenInput(),
}
class KPsulAccountForm(forms.ModelForm):
class Meta:
model = Account
fields = ['trigramme']
widgets = {
'trigramme': forms.TextInput(
attrs={
'autocomplete': 'off',
'spellcheck': 'false',
}),
}
class KPsulCheckoutForm(forms.Form):
checkout = forms.ModelChoiceField(
queryset=Checkout.objects.filter(
is_protected=False, valid_from__lte=timezone.now(),
valid_to__gte=timezone.now()),
widget=forms.Select(attrs={'id':'id_checkout_select'}))
class KPsulOperationForm(forms.ModelForm):
article = forms.ModelChoiceField(
queryset=Article.objects.select_related('category').all(),
required=False,
widget = forms.HiddenInput())
class Meta:
model = Operation
fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb']
widgets = {
'type': forms.HiddenInput(),
'amount': forms.HiddenInput(),
'is_checkout': forms.HiddenInput(),
'article_nb': forms.HiddenInput(),
}
def clean(self):
super(KPsulOperationForm, self).clean()
type_ope = self.cleaned_data.get('type')
amount = self.cleaned_data.get('amount')
article = self.cleaned_data.get('article')
article_nb = self.cleaned_data.get('article_nb')
if type_ope and type_ope == Operation.PURCHASE:
if not article or not article_nb:
raise ValidationError(
"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")
if type_ope == Operation.DEPOSIT and amount <= 0:
raise ValidationError("Charge non positive")
if type_ope == Operation.WITHDRAW and amount >= 0:
raise ValidationError("Retrait non négatif")
self.cleaned_data['article'] = None
self.cleaned_data['article_nb'] = None
KPsulOperationFormSet = modelformset_factory(
Operation,
form = KPsulOperationForm,
can_delete = True,
extra = 0,
min_num = 1, validate_min = True)
class AddcostForm(forms.Form):
trigramme = forms.CharField(required = False)
amount = forms.DecimalField(
required = False,
max_digits=6,decimal_places=2,min_value=Decimal(0))
def clean(self):
trigramme = self.cleaned_data.get('trigramme')
if trigramme:
try:
Account.objects.get(trigramme=trigramme)
except Account.DoesNotExist:
raise ValidationError('Compte invalide')
else:
self.cleaned_data['amount'] = 0
super(AddcostForm, self).clean()
# -----
# Settings forms
# -----
class SettingsForm(forms.ModelForm):
class Meta:
model = Settings
fields = ['value_decimal', 'value_account', 'value_duration']
def clean(self):
name = self.instance.name
value_decimal = self.cleaned_data.get('value_decimal')
value_account = self.cleaned_data.get('value_account')
value_duration = self.cleaned_data.get('value_duration')
type_decimal = ['SUBVENTION_COF', 'ADDCOST_AMOUNT', 'OVERDRAFT_AMOUNT']
type_account = ['ADDCOST_FOR']
type_duration = ['OVERDRAFT_DURATION', 'CANCEL_DURATION']
self.cleaned_data['name'] = name
if name in type_decimal:
if not value_decimal:
raise ValidationError('Renseignez une valeur décimale')
self.cleaned_data['value_account'] = None
self.cleaned_data['value_duration'] = None
elif name in type_account:
self.cleaned_data['value_decimal'] = None
self.cleaned_data['value_duration'] = None
elif name in type_duration:
if not value_duration:
raise ValidationError('Renseignez une durée')
self.cleaned_data['value_decimal'] = None
self.cleaned_data['value_account'] = None
super(SettingsForm, self).clean()
class FilterHistoryForm(forms.Form):
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())
accounts = forms.ModelMultipleChoiceField(queryset = Account.objects.all())
# -----
# Transfer forms
# -----
class TransferGroupForm(forms.ModelForm):
class Meta:
model = TransferGroup
fields = ['comment']
class TransferForm(forms.ModelForm):
from_acc = forms.ModelChoiceField(
queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']),
widget = forms.HiddenInput()
)
to_acc = forms.ModelChoiceField(
queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']),
widget = forms.HiddenInput()
)
def clean_amount(self):
amount = self.cleaned_data['amount']
if amount <= 0:
raise forms.ValidationError("Montant invalide")
return amount
class Meta:
model = Transfer
fields = ['from_acc', 'to_acc', 'amount']
TransferFormSet = modelformset_factory(
Transfer,
form = TransferForm,
min_num = 1, validate_min = True,
extra = 9,
)
# -----
# Inventory forms
# -----
class InventoryArticleForm(forms.Form):
article = forms.ModelChoiceField(
queryset = Article.objects.all(),
widget = forms.HiddenInput(),
)
stock_new = forms.IntegerField(required = False)
def __init__(self, *args, **kwargs):
super(InventoryArticleForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
self.name = kwargs['initial']['name']
self.stock_old = kwargs['initial']['stock_old']
self.category = kwargs['initial']['category']
self.category_name = kwargs['initial']['category__name']
# -----
# Order forms
# -----
class OrderArticleForm(forms.Form):
article = forms.ModelChoiceField(
queryset = Article.objects.all(),
widget = forms.HiddenInput(),
)
quantity_ordered = forms.IntegerField(required = False)
def __init__(self, *args, **kwargs):
super(OrderArticleForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
self.name = kwargs['initial']['name']
self.stock = kwargs['initial']['stock']
self.category = kwargs['initial']['category']
self.category_name = kwargs['initial']['category__name']
self.box_capacity = kwargs['initial']['box_capacity']
self.v_s1 = kwargs['initial']['v_s1']
self.v_s2 = kwargs['initial']['v_s2']
self.v_s3 = kwargs['initial']['v_s3']
self.v_s4 = kwargs['initial']['v_s4']
self.v_s5 = kwargs['initial']['v_s5']
self.v_moy = kwargs['initial']['v_moy']
self.v_et = kwargs['initial']['v_et']
self.v_prev = kwargs['initial']['v_prev']
self.c_rec = kwargs['initial']['c_rec']
class OrderArticleToInventoryForm(forms.Form):
article = forms.ModelChoiceField(
queryset = Article.objects.all(),
widget = forms.HiddenInput(),
)
price_HT = forms.DecimalField(
max_digits = 7, decimal_places = 4,
required = False)
TVA = forms.DecimalField(
max_digits = 7, decimal_places = 2,
required = False)
rights = forms.DecimalField(
max_digits = 7, decimal_places = 4,
required = False)
quantity_received = forms.IntegerField()
def __init__(self, *args, **kwargs):
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
self.name = kwargs['initial']['name']
self.category = kwargs['initial']['category']
self.category_name = kwargs['initial']['category__name']
self.quantity_ordered = kwargs['initial']['quantity_ordered']

17
kfet/middleware.py Normal file
View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.http import HttpResponseForbidden
from kfet.backends import KFetBackend
from kfet.models import Account
class KFetAuthenticationMiddleware(object):
def process_request(self, request):
kfet_backend = KFetBackend()
temp_request_user = kfet_backend.authenticate(request)
if temp_request_user:
request.real_user = request.user
request.user = temp_request_user

View file

@ -0,0 +1,257 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
import django.core.validators
import datetime
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0007_alter_club'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('trigramme', models.CharField(max_length=3, validators=[django.core.validators.RegexValidator(regex='^[^a-z]{3}$')], unique=True)),
('balance', models.DecimalField(decimal_places=2, default=0, max_digits=6)),
('frozen', models.BooleanField(default=False)),
('promo', models.IntegerField(null=True, blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016)], default=2015)),
('nickname', models.CharField(max_length=255, blank=True, default='')),
('password', models.CharField(max_length=255, blank=True, null=True, unique=True, default=None)),
('cofprofile', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='account_kfet', to='gestioncof.CofProfile')),
],
),
migrations.CreateModel(
name='AccountNegative',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start', models.DateTimeField(default=datetime.datetime(2016, 8, 2, 10, 22, 1, 569492))),
('balance_offset', models.DecimalField(decimal_places=2, max_digits=6)),
('authorized_overdraft', models.DecimalField(decimal_places=2, default=0, max_digits=6)),
('comment', models.CharField(max_length=255, blank=True)),
('account', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='negative', to='kfet.Account')),
],
),
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=45)),
('is_sold', models.BooleanField(default=True)),
('price', models.DecimalField(decimal_places=2, max_digits=6)),
('stock', models.IntegerField(default=0)),
],
),
migrations.CreateModel(
name='ArticleCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=45)),
],
),
migrations.CreateModel(
name='ArticleRule',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ratio', models.PositiveSmallIntegerField()),
('article_on', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='rule_on', to='kfet.Article')),
('article_to', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='rule_to', to='kfet.Article')),
],
),
migrations.CreateModel(
name='Checkout',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=45)),
('valid_from', models.DateTimeField()),
('valid_to', models.DateTimeField()),
('balance', models.DecimalField(decimal_places=2, max_digits=6)),
('is_protected', models.BooleanField(default=False)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account')),
],
),
migrations.CreateModel(
name='CheckoutTransfer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=6)),
('from_checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_from', to='kfet.Checkout')),
('to_checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_to', to='kfet.Checkout')),
],
),
migrations.CreateModel(
name='Inventory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='InventoryArticle',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stock_old', models.IntegerField()),
('stock_new', models.IntegerField()),
('stock_error', models.IntegerField(default=0)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')),
('inventory', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Inventory')),
],
),
migrations.CreateModel(
name='Operation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=8, choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait')])),
('amount', models.DecimalField(decimal_places=2, max_digits=6)),
('on_checkout', models.BooleanField(default=True)),
('canceled_at', models.DateTimeField(blank=True, null=True, default=None)),
('addcost_amount', models.DecimalField(decimal_places=2, max_digits=6)),
('addcost_for', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='addcosts', to='kfet.Account', null=True, default=None)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='operations', to='kfet.Article', null=True, default=None)),
('canceled_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)),
],
),
migrations.CreateModel(
name='OperationGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('at', models.DateTimeField(auto_now_add=True)),
('amount', models.IntegerField()),
('is_cof', models.BooleanField(default=False)),
('comment', models.CharField(max_length=255, blank=True, default='')),
('checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='operations', to='kfet.Checkout')),
('on_acc', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='operations', to='kfet.Account')),
('valid_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=True)),
],
),
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('at', models.DateTimeField(auto_now_add=True)),
('amount', models.DecimalField(decimal_places=2, max_digits=6)),
],
),
migrations.CreateModel(
name='OrderArticle',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity_ordered', models.IntegerField()),
('quantity_received', models.IntegerField()),
('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Order')),
],
),
migrations.CreateModel(
name='Statement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('balance_old', models.DecimalField(decimal_places=2, max_digits=6)),
('balance_new', models.DecimalField(decimal_places=2, max_digits=6)),
('amount_taken', models.DecimalField(decimal_places=2, max_digits=6)),
('amount_error', models.DecimalField(decimal_places=2, max_digits=6)),
('at', models.DateTimeField(auto_now_add=True)),
('by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account')),
('checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='statements', to='kfet.Checkout')),
],
),
migrations.CreateModel(
name='Supplier',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=45)),
('address', models.TextField()),
('email', models.EmailField(max_length=254)),
('phone', models.CharField(max_length=10)),
('comment', models.TextField()),
],
),
migrations.CreateModel(
name='SupplierArticle',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('box_type', models.CharField(max_length=7, choices=[('caisse', 'Caisse'), ('carton', 'Carton'), ('palette', 'Palette'), ('fût', 'Fût')])),
('box_capacity', models.PositiveSmallIntegerField()),
('price_HT', models.DecimalField(decimal_places=4, max_digits=7)),
('TVA', models.DecimalField(decimal_places=2, max_digits=4)),
('rights', models.DecimalField(decimal_places=4, max_digits=7)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')),
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Supplier')),
],
),
migrations.CreateModel(
name='Transfer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=6)),
('canceled_at', models.DateTimeField(blank=True, null=True, default=None)),
('canceled_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)),
('from_acc', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_from', to='kfet.Account')),
],
),
migrations.CreateModel(
name='TransferGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('at', models.DateTimeField(auto_now_add=True)),
('comment', models.CharField(max_length=255, blank=True, default='')),
('valid_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)),
],
),
migrations.AddField(
model_name='transfer',
name='group',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers', to='kfet.TransferGroup'),
),
migrations.AddField(
model_name='transfer',
name='to_acc',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_to', to='kfet.Account'),
),
migrations.AddField(
model_name='supplier',
name='articles',
field=models.ManyToManyField(related_name='suppliers', through='kfet.SupplierArticle', to='kfet.Article'),
),
migrations.AddField(
model_name='order',
name='articles',
field=models.ManyToManyField(related_name='orders', through='kfet.OrderArticle', to='kfet.Article'),
),
migrations.AddField(
model_name='order',
name='supplier',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='orders', to='kfet.Supplier'),
),
migrations.AddField(
model_name='operation',
name='group',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.OperationGroup'),
),
migrations.AddField(
model_name='inventory',
name='articles',
field=models.ManyToManyField(related_name='inventories', through='kfet.InventoryArticle', to='kfet.Article'),
),
migrations.AddField(
model_name='inventory',
name='by',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account'),
),
migrations.AddField(
model_name='inventory',
name='order',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='inventory', to='kfet.Order', null=True, default=None),
),
migrations.AddField(
model_name='article',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='articles', to='kfet.ArticleCategory'),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('kfet', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='account',
options={'permissions': (('is_team', 'Is part of the team'),)},
),
migrations.AlterField(
model_name='accountnegative',
name='start',
field=models.DateTimeField(default=datetime.datetime(2016, 8, 2, 21, 39, 30, 52279)),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('kfet', '0002_auto_20160802_2139'),
]
operations = [
migrations.AlterField(
model_name='accountnegative',
name='start',
field=models.DateTimeField(default=datetime.datetime.now),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0003_auto_20160802_2142'),
]
operations = [
migrations.AlterField(
model_name='accountnegative',
name='balance_offset',
field=models.DecimalField(decimal_places=2, max_digits=6, default=0),
),
]

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0004_auto_20160802_2144'),
]
operations = [
migrations.CreateModel(
name='GlobalPermissions',
fields=[
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
],
options={
'permissions': (('is_team', 'Is part of the team'),),
'managed': False,
},
),
migrations.AlterModelOptions(
name='account',
options={},
),
]

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0005_auto_20160802_2154'),
]
operations = [
migrations.AlterModelOptions(
name='checkout',
options={'ordering': ['-valid_to']},
),
migrations.RenameField(
model_name='account',
old_name='frozen',
new_name='is_frozen',
),
migrations.AlterField(
model_name='checkout',
name='balance',
field=models.DecimalField(max_digits=6, default=0, decimal_places=2),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0006_auto_20160804_0600'),
]
operations = [
migrations.AlterField(
model_name='article',
name='price',
field=models.DecimalField(default=0, max_digits=6, decimal_places=2),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('kfet', '0007_auto_20160804_0641'),
]
operations = [
migrations.AlterField(
model_name='account',
name='trigramme',
field=models.CharField(unique=True, validators=[django.core.validators.RegexValidator(regex='^[^a-z]{3}$')], db_index=True, max_length=3),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0008_auto_20160804_1736'),
]
operations = [
migrations.RenameField(
model_name='operation',
old_name='on_checkout',
new_name='is_checkout',
),
migrations.AddField(
model_name='operation',
name='article_nb',
field=models.PositiveSmallIntegerField(default=None, null=True, blank=True),
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kfet', '0009_auto_20160805_0720'),
]
operations = [
migrations.AlterField(
model_name='operation',
name='addcost_amount',
field=models.DecimalField(max_digits=6, default=0, decimal_places=2),
),
migrations.AlterField(
model_name='operationgroup',
name='amount',
field=models.DecimalField(max_digits=6, default=0, decimal_places=2),
),
migrations.AlterField(
model_name='operationgroup',
name='valid_by',
field=models.ForeignKey(default=None, related_name='+', to='kfet.Account', blank=True, null=True, on_delete=django.db.models.deletion.PROTECT),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0010_auto_20160806_2343'),
]
operations = [
migrations.AlterField(
model_name='operation',
name='amount',
field=models.DecimalField(decimal_places=2, max_digits=6, default=0, blank=True),
),
]

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0011_auto_20160807_1720'),
]
operations = [
migrations.CreateModel(
name='Settings',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('name', models.CharField(max_length=45)),
('value_decimal', models.DecimalField(null=True, max_digits=6, decimal_places=2, blank=True, default=None)),
],
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0012_settings'),
]
operations = [
migrations.AlterField(
model_name='settings',
name='name',
field=models.CharField(unique=True, max_length=45),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0013_auto_20160807_1840'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('can_perform_deposit', 'Peut effectuer une charge')), 'managed': False},
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0014_auto_20160807_2314'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('can_perform_deposit', 'Peut effectuer une charge'), ('can_perform_negative_operations', 'Peut enregistrer des commandes en négatif')), 'managed': False},
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kfet', '0015_auto_20160807_2324'),
]
operations = [
migrations.AddField(
model_name='settings',
name='value_account',
field=models.ForeignKey(to='kfet.Account', on_delete=django.db.models.deletion.PROTECT, default=None, null=True, blank=True),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0016_settings_value_account'),
]
operations = [
migrations.AlterField(
model_name='operation',
name='addcost_amount',
field=models.DecimalField(blank=True, null=True, decimal_places=2, default=None, max_digits=6),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0017_auto_20160808_0234'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('can_perform_deposit', 'Effectuer une charge'), ('can_perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte")), 'managed': False},
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0018_auto_20160808_0341'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"))},
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('kfet', '0019_auto_20160808_0343'),
]
operations = [
migrations.AlterField(
model_name='accountnegative',
name='start',
field=models.DateTimeField(default=datetime.datetime.now, blank=True, null=True),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0020_auto_20160808_0450'),
]
operations = [
migrations.AlterField(
model_name='accountnegative',
name='start',
field=models.DateTimeField(default=None, blank=True, null=True),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0021_auto_20160808_0506'),
]
operations = [
migrations.AlterField(
model_name='accountnegative',
name='authorized_overdraft',
field=models.DecimalField(blank=True, decimal_places=2, null=True, default=None, max_digits=6),
),
migrations.AlterField(
model_name='accountnegative',
name='balance_offset',
field=models.DecimalField(blank=True, decimal_places=2, null=True, default=None, max_digits=6),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0022_auto_20160808_0512'),
]
operations = [
migrations.RenameField(
model_name='accountnegative',
old_name='authorized_overdraft',
new_name='authz_overdraft_amount',
),
migrations.AddField(
model_name='accountnegative',
name='authz_overdraft_until',
field=models.DateTimeField(null=True, default=None, blank=True),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0023_auto_20160808_0535'),
]
operations = [
migrations.AddField(
model_name='settings',
name='value_duration',
field=models.DurationField(null=True, default=None, blank=True),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0024_settings_value_duration'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes')), 'managed': False},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0025_auto_20160809_0750'),
]
operations = [
migrations.AlterField(
model_name='settings',
name='name',
field=models.CharField(db_index=True, max_length=45, unique=True),
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kfet', '0026_auto_20160809_0810'),
]
operations = [
migrations.CreateModel(
name='CheckoutStatement',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('balance_old', models.DecimalField(decimal_places=2, max_digits=6)),
('balance_new', models.DecimalField(decimal_places=2, max_digits=6)),
('amount_taken', models.DecimalField(decimal_places=2, max_digits=6)),
('amount_error', models.DecimalField(decimal_places=2, max_digits=6)),
('at', models.DateTimeField(auto_now_add=True)),
('by', models.ForeignKey(to='kfet.Account', on_delete=django.db.models.deletion.PROTECT, related_name='+')),
('checkout', models.ForeignKey(to='kfet.Checkout', on_delete=django.db.models.deletion.PROTECT, related_name='statements')),
],
),
migrations.RemoveField(
model_name='statement',
name='by',
),
migrations.RemoveField(
model_name='statement',
name='checkout',
),
migrations.DeleteModel(
name='Statement',
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('kfet', '0027_auto_20160811_0648'),
]
operations = [
migrations.AlterField(
model_name='operation',
name='group',
field=models.ForeignKey(to='kfet.OperationGroup', on_delete=django.db.models.deletion.PROTECT, related_name='opes'),
),
migrations.AlterField(
model_name='operationgroup',
name='checkout',
field=models.ForeignKey(to='kfet.Checkout', on_delete=django.db.models.deletion.PROTECT, related_name='opesgroup'),
),
migrations.AlterField(
model_name='operationgroup',
name='on_acc',
field=models.ForeignKey(to='kfet.Account', on_delete=django.db.models.deletion.PROTECT, related_name='opesgroup'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0028_auto_20160820_0146'),
]
operations = [
migrations.CreateModel(
name='GenericTeamToken',
fields=[
('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')),
('token', models.CharField(unique=True, max_length=50)),
],
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0029_genericteamtoken'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt')), 'managed': False},
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0030_auto_20160821_0029'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations')), 'managed': False},
),
]

View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0031_auto_20160822_0523'),
]
operations = [
migrations.AddField(
model_name='checkoutstatement',
name='taken_001',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_002',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_005',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_01',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_02',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_05',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_1',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_10',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_100',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_2',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_20',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_200',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_5',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_50',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_500',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='checkoutstatement',
name='taken_cheque',
field=models.PositiveSmallIntegerField(default=0),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0032_auto_20160822_2350'),
]
operations = [
migrations.AddField(
model_name='checkoutstatement',
name='not_count',
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0033_checkoutstatement_not_count'),
]
operations = [
migrations.AlterField(
model_name='checkoutstatement',
name='taken_cheque',
field=models.DecimalField(max_digits=6, decimal_places=2, default=0),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0034_auto_20160823_0206'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'))},
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0035_auto_20160823_1505'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'))},
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0036_auto_20160823_1910'),
]
operations = [
migrations.AlterField(
model_name='supplierarticle',
name='TVA',
field=models.DecimalField(null=True, max_digits=4, decimal_places=2, default=None, blank=True),
),
migrations.AlterField(
model_name='supplierarticle',
name='box_capacity',
field=models.PositiveSmallIntegerField(null=True, default=None, blank=True),
),
migrations.AlterField(
model_name='supplierarticle',
name='box_type',
field=models.CharField(null=True, max_length=7, choices=[('caisse', 'Caisse'), ('carton', 'Carton'), ('palette', 'Palette'), ('fût', 'Fût')], default=None, blank=True),
),
migrations.AlterField(
model_name='supplierarticle',
name='price_HT',
field=models.DecimalField(null=True, max_digits=7, decimal_places=4, default=None, blank=True),
),
migrations.AlterField(
model_name='supplierarticle',
name='rights',
field=models.DecimalField(null=True, max_digits=7, decimal_places=4, default=None, blank=True),
),
]

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0037_auto_20160826_2333'),
]
operations = [
migrations.AlterModelOptions(
name='inventory',
options={'ordering': ['-at']},
),
migrations.RemoveField(
model_name='supplierarticle',
name='box_capacity',
),
migrations.RemoveField(
model_name='supplierarticle',
name='box_type',
),
migrations.AddField(
model_name='article',
name='box_capacity',
field=models.PositiveSmallIntegerField(blank=True, null=True, default=None),
),
migrations.AddField(
model_name='article',
name='box_type',
field=models.CharField(max_length=7, blank=True, null=True, default=None, choices=[('caisse', 'Caisse'), ('carton', 'Carton'), ('palette', 'Palette'), ('fût', 'Fût')]),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0038_auto_20160828_0402'),
]
operations = [
migrations.AlterField(
model_name='order',
name='amount',
field=models.DecimalField(default=0, decimal_places=2, max_digits=6),
),
migrations.AlterField(
model_name='orderarticle',
name='quantity_received',
field=models.IntegerField(default=0),
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('kfet', '0039_auto_20160828_0430'),
]
operations = [
migrations.AlterModelOptions(
name='order',
options={'ordering': ['-at']},
),
migrations.AddField(
model_name='supplierarticle',
name='at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 8, 29, 18, 35, 3, 419033, tzinfo=utc)),
preserve_default=False,
),
migrations.AlterField(
model_name='article',
name='box_type',
field=models.CharField(choices=[('caisse', 'caisse'), ('carton', 'carton'), ('palette', 'palette'), ('fût', 'fût')], null=True, max_length=7, blank=True, default=None),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0040_auto_20160829_2035'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande")), 'managed': False},
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0041_auto_20160830_1502'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande"), ('edit_balance_account', "Modifier la balance d'un compte"))},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0042_auto_20160831_0126'),
]
operations = [
migrations.AlterField(
model_name='account',
name='promo',
field=models.IntegerField(blank=True, default=2016, null=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016)]),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0043_auto_20160901_0046'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en n\xe9gatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non r\xe9centes'), ('manage_perms', 'G\xe9rer les permissions K-F\xeat'), ('manage_addcosts', 'G\xe9rer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des n\xe9gatifs'), ('order_to_inventory', "G\xe9n\xe9rer un inventaire \xe0 partir d'une commande"), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'\xe9quipe"))},
),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0044_auto_20160901_1614'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en n\xe9gatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non r\xe9centes'), ('manage_perms', 'G\xe9rer les permissions K-F\xeat'), ('manage_addcosts', 'G\xe9rer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des n\xe9gatifs'), ('order_to_inventory', "G\xe9n\xe9rer un inventaire \xe0 partir d'une commande"), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'\xe9quipe"), ('special_add_account', 'Cr\xe9er un compte avec une balance initiale'))},
),
migrations.AlterField(
model_name='operation',
name='type',
field=models.CharField(max_length=8, choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial')]),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0045_auto_20160905_0705'),
]
operations = [
migrations.AddField(
model_name='account',
name='created_at',
field=models.DateTimeField(auto_now_add=True, null=True),
),
]

View file

684
kfet/models.py Normal file
View file

@ -0,0 +1,684 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.db import models
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.validators import RegexValidator
from django.contrib.auth.models import User
from gestioncof.models import CofProfile
from django.utils.six.moves import reduce
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.db import transaction
from django.db.models import F
from django.core.cache import cache
from datetime import date, timedelta
import re
def choices_length(choices):
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
def default_promo():
now = date.today()
return now.month <= 8 and now.year-1 or now.year
@python_2_unicode_compatible
class Account(models.Model):
cofprofile = models.OneToOneField(
CofProfile, on_delete = models.PROTECT,
related_name = "account_kfet")
trigramme = models.CharField(
unique = True,
max_length = 3,
validators = [RegexValidator(regex='^[^a-z]{3}$')],
db_index = True)
balance = models.DecimalField(
max_digits = 6, decimal_places = 2,
default = 0)
is_frozen = models.BooleanField(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)]
promo = models.IntegerField(
choices = PROMO_CHOICES,
blank = True, null = True, default = default_promo())
nickname = models.CharField(
max_length = 255,
blank = True, default = "")
password = models.CharField(
max_length = 255,
unique = True,
blank = True, null = True, default = None)
def __str__(self):
return '%s (%s)' % (self.trigramme, self.name)
# Propriétés pour accéder aux attributs de user et cofprofile et user
@property
def user(self):
return self.cofprofile.user
@property
def username(self):
return self.cofprofile.user.username
@property
def first_name(self):
return self.cofprofile.user.first_name
@property
def last_name(self):
return self.cofprofile.user.last_name
@property
def email(self):
return self.cofprofile.user.email
@property
def departement(self):
return self.cofprofile.departement
@property
def is_cof(self):
return self.cofprofile.is_cof
# Propriétés supplémentaires
@property
def real_balance(self):
if (hasattr(self, 'negative')):
return self.balance - self.negative.balance_offset
return self.balance
@property
def name(self):
return self.user.get_full_name()
@property
def is_cash(self):
return self.trigramme == 'LIQ'
@property
def need_comment(self):
return self.trigramme == '#13'
@staticmethod
def is_validandfree(trigramme):
data = { 'is_valid' : False, 'is_free' : False }
pattern = re.compile("^[^a-z]{3}$")
data['is_valid'] = pattern.match(trigramme) and True or False
try:
account = Account.objects.get(trigramme=trigramme)
except Account.DoesNotExist:
data['is_free'] = True
return data
def perms_to_perform_operation(self, amount):
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
perms = set()
stop_ope = False
# Checking is cash account
if self.is_cash:
# Yes, so no perms and no stop
return set(), False
if self.need_comment:
perms.add('kfet.perform_commented_operations')
# Checking is frozen account
if self.is_frozen:
perms.add('kfet.override_frozen_protection')
new_balance = self.balance + amount
if new_balance < 0 and amount < 0:
# Retrieving overdraft amount limit
if (hasattr(self, 'negative')
and self.negative.authz_overdraft_amount is not None):
overdraft_amount = - self.negative.authz_overdraft_amount
else:
overdraft_amount = - overdraft_amount_max
# Retrieving overdraft datetime limit
if (hasattr(self, 'negative')
and self.negative.authz_overdraft_until is not None):
overdraft_until = self.negative.authz_overdraft_until
elif hasattr(self, 'negative'):
overdraft_until = \
self.negative.start + overdraft_duration_max
else:
overdraft_until = timezone.now() + overdraft_duration_max
# Checking it doesn't break 1 rule
if new_balance < overdraft_amount or timezone.now() > overdraft_until:
stop_ope = True
perms.add('kfet.perform_negative_operations')
return perms, stop_ope
# Surcharge Méthode save() avec gestions de User et CofProfile
# Args:
# - data : datas pour User et CofProfile
# Action:
# - Enregistre User, CofProfile à partir de "data"
# - Enregistre Account
def save(self, data = {}, *args, **kwargs):
if self.pk and data:
# Account update
# Updating User with data
user = self.user
user.first_name = data.get("first_name", user.first_name)
user.last_name = data.get("last_name", user.last_name)
user.email = data.get("email", user.email)
user.save()
# Updating CofProfile with data
cof = self.cofprofile
cof.departement = data.get("departement", cof.departement)
cof.save()
elif data:
# New account
# Checking if user has already an account
username = data.get("username")
try:
user = User.objects.get(username=username)
if hasattr(user.profile, "account_kfet"):
trigramme = user.profile.account_kfet.trigramme
raise Account.UserHasAccount(trigramme)
except User.DoesNotExist:
pass
# Creating or updating User instance
(user, _) = User.objects.get_or_create(username=username)
if "first_name" in data:
user.first_name = data['first_name']
if "last_name" in data:
user.last_name = data['last_name']
if "email" in data:
user.email = data['email']
user.save()
# Creating or updating CofProfile instance
(cof, _) = CofProfile.objects.get_or_create(user=user)
if "login_clipper" in data:
cof.login_clipper = data['login_clipper']
if "departement" in data:
cof.departement = data['departement']
cof.save()
if data:
self.cofprofile = cof
super(Account, self).save(*args, **kwargs)
# Surcharge de delete
# Pas de suppression possible
# Cas à régler plus tard
def delete(self, *args, **kwargs):
pass
class UserHasAccount(Exception):
def __init__(self, trigramme):
self.trigramme = trigramme
class AccountNegative(models.Model):
account = models.OneToOneField(
Account, on_delete = models.PROTECT,
related_name = "negative")
start = models.DateTimeField(
blank = True, null = True, default = None)
balance_offset = models.DecimalField(
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
authz_overdraft_amount = models.DecimalField(
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
authz_overdraft_until = models.DateTimeField(
blank = True, null = True, default = None)
comment = models.CharField(max_length = 255, blank = True)
@python_2_unicode_compatible
class Checkout(models.Model):
created_by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+")
name = models.CharField(max_length = 45)
valid_from = models.DateTimeField()
valid_to = models.DateTimeField()
balance = models.DecimalField(
max_digits = 6, decimal_places = 2,
default = 0)
is_protected = models.BooleanField(default = False)
def get_absolute_url(self):
return reverse('kfet.checkout.read', kwargs={'pk': self.pk})
class Meta:
ordering = ['-valid_to']
def __str__(self):
return self.name
class CheckoutTransfer(models.Model):
from_checkout = models.ForeignKey(
Checkout, on_delete = models.PROTECT,
related_name = "transfers_from")
to_checkout = models.ForeignKey(
Checkout, on_delete = models.PROTECT,
related_name = "transfers_to")
amount = models.DecimalField(
max_digits = 6, decimal_places = 2)
@python_2_unicode_compatible
class CheckoutStatement(models.Model):
by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+")
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)
at = models.DateTimeField(auto_now_add = True)
not_count = models.BooleanField(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)
def __str__(self):
return '%s %s' % (self.checkout, self.at)
def save(self, *args, **kwargs):
if not self.pk:
checkout_id = self.checkout_id
self.balance_old = (Checkout.objects
.values_list('balance', flat=True).get(pk=checkout_id))
if self.not_count:
self.balance_new = self.balance_old - self.amount_taken
self.amount_error = (
self.balance_new + self.amount_taken - self.balance_old)
with transaction.atomic():
Checkout.objects.filter(pk=checkout_id).update(balance=self.balance_new)
super(CheckoutStatement, self).save(*args, **kwargs)
else:
self.amount_error = (
self.balance_new + self.amount_taken - self.balance_old)
# Si on modifie le dernier relevé d'une caisse et que la nouvelle
# balance est modifiée alors on modifie la balance actuelle de la caisse
last_statement = (CheckoutStatement.objects
.filter(checkout=self.checkout)
.order_by('at')
.last())
if (last_statement.pk == self.pk
and last_statement.balance_new != self.balance_new):
Checkout.objects.filter(pk=self.checkout_id).update(
balance=F('balance') - last_statement.balance_new + self.balance_new)
super(CheckoutStatement, self).save(*args, **kwargs)
@python_2_unicode_compatible
class ArticleCategory(models.Model):
name = models.CharField(max_length = 45)
def __str__(self):
return self.name
@python_2_unicode_compatible
class Article(models.Model):
name = models.CharField(max_length = 45)
is_sold = models.BooleanField(default = True)
price = models.DecimalField(
max_digits = 6, decimal_places = 2,
default = 0)
stock = models.IntegerField(default = 0)
category = models.ForeignKey(
ArticleCategory, on_delete = models.PROTECT,
related_name = "articles")
BOX_TYPE_CHOICES = (
("caisse", "caisse"),
("carton", "carton"),
("palette", "palette"),
("fût", "fût"),
)
box_type = models.CharField(
choices = BOX_TYPE_CHOICES,
max_length = choices_length(BOX_TYPE_CHOICES),
blank = True, null = True, default = None)
box_capacity = models.PositiveSmallIntegerField(
blank = True, null = True, default = None)
def __str__(self):
return '%s - %s' % (self.category.name, self.name)
def get_absolute_url(self):
return reverse('kfet.article.read', kwargs={'pk': self.pk})
class ArticleRule(models.Model):
article_on = models.OneToOneField(
Article, on_delete = models.PROTECT,
related_name = "rule_on")
article_to = models.OneToOneField(
Article, on_delete = models.PROTECT,
related_name = "rule_to")
ratio = models.PositiveSmallIntegerField()
class Inventory(models.Model):
articles = models.ManyToManyField(
Article,
through = 'InventoryArticle',
related_name = "inventories")
by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+")
at = models.DateTimeField(auto_now_add = True)
# Optional
order = models.OneToOneField(
'Order', on_delete = models.PROTECT,
related_name = "inventory",
blank = True, null = True, default = None)
class Meta:
ordering = ['-at']
class InventoryArticle(models.Model):
inventory = models.ForeignKey(
Inventory, on_delete = models.PROTECT)
article = models.ForeignKey(
Article, on_delete = models.PROTECT)
stock_old = models.IntegerField()
stock_new = models.IntegerField()
stock_error = models.IntegerField(default = 0)
def save(self, *args, **kwargs):
# S'il s'agit d'un inventaire provenant d'une livraison, il n'y a pas
# d'erreur
if not self.inventory.order:
self.stock_error = self.stock_new - self.stock_old
super(InventoryArticle, self).save(*args, **kwargs)
@python_2_unicode_compatible
class Supplier(models.Model):
articles = models.ManyToManyField(
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()
def __str__(self):
return self.name
class SupplierArticle(models.Model):
supplier = models.ForeignKey(
Supplier, on_delete = models.PROTECT)
article = models.ForeignKey(
Article, on_delete = models.PROTECT)
at = models.DateTimeField(auto_now_add = True)
price_HT = models.DecimalField(
max_digits = 7, decimal_places = 4,
blank = True, null = True, default = None)
TVA = models.DecimalField(
max_digits = 4, decimal_places = 2,
blank = True, null = True, default = None)
rights = models.DecimalField(
max_digits = 7, decimal_places = 4,
blank = True, null = True, default = None)
class Order(models.Model):
supplier = models.ForeignKey(
Supplier, on_delete = models.PROTECT,
related_name = "orders")
articles = models.ManyToManyField(
Article,
through = "OrderArticle",
related_name = "orders")
at = models.DateTimeField(auto_now_add = True)
amount = models.DecimalField(
max_digits = 6, decimal_places = 2, default = 0)
class Meta:
ordering = ['-at']
class OrderArticle(models.Model):
order = models.ForeignKey(
Order, on_delete = models.PROTECT)
article = models.ForeignKey(
Article, on_delete = models.PROTECT)
quantity_ordered = models.IntegerField()
quantity_received = models.IntegerField(default = 0)
class TransferGroup(models.Model):
at = models.DateTimeField(auto_now_add = True)
# Optional
comment = models.CharField(
max_length = 255,
blank = True, default = "")
valid_by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+",
blank = True, null = True, default = None)
class Transfer(models.Model):
group = models.ForeignKey(
TransferGroup, on_delete = models.PROTECT,
related_name = "transfers")
from_acc = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "transfers_from")
to_acc = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "transfers_to")
amount = models.DecimalField(max_digits = 6, decimal_places = 2)
# Optional
canceled_by = models.ForeignKey(
Account, on_delete = models.PROTECT,
null = True, blank = True, default = None,
related_name = "+")
canceled_at = models.DateTimeField(
null = True, blank = True, default = None)
class OperationGroup(models.Model):
on_acc = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "opesgroup")
checkout = models.ForeignKey(
Checkout, on_delete = models.PROTECT,
related_name = "opesgroup")
at = models.DateTimeField(auto_now_add = True)
amount = models.DecimalField(
max_digits = 6, decimal_places = 2,
default = 0)
is_cof = models.BooleanField(default = False)
# Optional
comment = models.CharField(
max_length = 255,
blank = True, default = "")
valid_by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+",
blank = True, null = True, default = None)
class Operation(models.Model):
PURCHASE = 'purchase'
DEPOSIT = 'deposit'
WITHDRAW = 'withdraw'
INITIAL = 'initial'
TYPE_ORDER_CHOICES = (
(PURCHASE, 'Achat'),
(DEPOSIT, 'Charge'),
(WITHDRAW, 'Retrait'),
(INITIAL, 'Initial'),
)
group = models.ForeignKey(
OperationGroup, on_delete = models.PROTECT,
related_name = "opes")
type = models.CharField(
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)
# Optional
article = models.ForeignKey(
Article, on_delete = models.PROTECT,
related_name = "operations",
blank = True, null = True, default = None)
article_nb = models.PositiveSmallIntegerField(
blank = True, null = True, default = None)
canceled_by = models.ForeignKey(
Account, on_delete = models.PROTECT,
related_name = "+",
blank = True, null = True, default = None)
canceled_at = models.DateTimeField(
blank = True, null = True, default = None)
addcost_for = models.ForeignKey(
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)
class GlobalPermissions(models.Model):
class Meta:
managed = False
permissions = (
('is_team', 'Is part of the team'),
('perform_deposit', 'Effectuer une charge'),
('perform_negative_operations',
'Enregistrer des commandes en négatif'),
('override_frozen_protection', "Forcer le gel d'un compte"),
('cancel_old_operations', 'Annuler des commandes non récentes'),
('manage_perms', 'Gérer les permissions K-Fêt'),
('manage_addcosts', 'Gérer les majorations'),
('perform_commented_operations', 'Enregistrer des commandes avec commentaires'),
('view_negs', 'Voir la liste des négatifs'),
('order_to_inventory', "Générer un inventaire à partir d'une commande"),
('edit_balance_account', "Modifier la balance d'un compte"),
('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"),
('special_add_account', "Créer un compte avec une balance initiale")
)
class Settings(models.Model):
name = models.CharField(
max_length = 45,
unique = True,
db_index = True)
value_decimal = models.DecimalField(
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
value_account = models.ForeignKey(
Account, on_delete = models.PROTECT,
blank = True, null = True, default = None)
value_duration = models.DurationField(
blank = True, null = True, default = None)
@staticmethod
def setting_inst(name):
return Settings.objects.get(name=name)
@staticmethod
def SUBVENTION_COF():
subvention_cof = cache.get('SUBVENTION_COF')
if subvention_cof:
return subvention_cof
try:
subvention_cof = Settings.setting_inst("SUBVENTION_COF").value_decimal
except Settings.DoesNotExist:
subvention_cof = 0
cache.set('SUBVENTION_COF', subvention_cof)
return subvention_cof
@staticmethod
def ADDCOST_AMOUNT():
try:
return Settings.setting_inst("ADDCOST_AMOUNT").value_decimal
except Settings.DoesNotExist:
return 0
@staticmethod
def ADDCOST_FOR():
try:
return Settings.setting_inst("ADDCOST_FOR").value_account
except Settings.DoesNotExist:
return None;
@staticmethod
def OVERDRAFT_DURATION():
overdraft_duration = cache.get('OVERDRAFT_DURATION')
if overdraft_duration:
return overdraft_duration
try:
overdraft_duration = Settings.setting_inst("OVERDRAFT_DURATION").value_duration
except Settings.DoesNotExist:
overdraft_duration = timedelta()
cache.set('OVERDRAFT_DURATION', overdraft_duration)
return overdraft_duration
@staticmethod
def OVERDRAFT_AMOUNT():
overdraft_amount = cache.get('OVERDRAFT_AMOUNT')
if overdraft_amount:
return overdraft_amount
try:
overdraft_amount = Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
except Settings.DoesNotExist:
overdraft_amount = 0
cache.set('OVERDRAFT_AMOUNT', overdraft_amount)
return overdraft_amount
@staticmethod
def CANCEL_DURATION():
cancel_duration = cache.get('CANCEL_DURATION')
if cancel_duration:
return cancel_duration
try:
cancel_duration = Settings.setting_inst("CANCEL_DURATION").value_duration
except Settings.DoesNotExist:
cancel_duration = timedelta()
cache.set('CANCEL_DURATION', cancel_duration)
return cancel_duration
@staticmethod
def create_missing():
s, created = Settings.objects.get_or_create(name='SUBVENTION_COF')
if created:
s.value_decimal = 25
s.save()
s, created = Settings.objects.get_or_create(name='ADDCOST_AMOUNT')
if created:
s.value_decimal = 0.5
s.save()
s, created = Settings.objects.get_or_create(name='ADDCOST_FOR')
s, created = Settings.objects.get_or_create(name='OVERDRAFT_DURATION')
if created:
s.value_duration = timedelta(days=1) # 24h
s.save()
s, created = Settings.objects.get_or_create(name='OVERDRAFT_AMOUNT')
if created:
s.value_decimal = 20
s.save()
s, created = Settings.objects.get_or_create(name='CANCEL_DURATION')
if created:
s.value_duration = timedelta(minutes=5) # 5min
s.save()
@staticmethod
def empty_cache():
cache.delete_many([
'SUBVENTION_COF', 'OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT',
'CANCEL_DURATION', 'ADDCOST_AMOUNT', 'ADDCOST_FOR',
])
class GenericTeamToken(models.Model):
token = models.CharField(max_length = 50, unique = True)

14
kfet/routing.py Normal file
View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from channels.routing import route, route_class
from kfet import consumers
channel_routing = [
route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"),
#route("websocket.connect", ws_kpsul_history_connect),
#route('websocket.receive', ws_message)
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,106 @@
#history {
font-family:'Roboto Mono';
}
#history span {
display:inline-block;
}
#history .day {
height:40px;
line-height:40px;
background-color:#c8102e;
color:#fff;
padding-left:20px;
font-size:16px;
font-weight:bold;
}
#history .opegroup {
height:30px;
line-height:30px;
background-color:rgba(200,16,46,0.85);
color:#fff;
font-weight:bold;
padding-left:20px;
overflow:auto;
}
#history .opegroup .time {
width:70px;
}
#history .opegroup .trigramme {
width:55px;
text-align:right;
}
#history .opegroup .amount {
text-align:right;
width:90px;
}
#history .opegroup .valid_by {
padding-left:20px
}
#history .opegroup .comment {
padding-left:20px;
}
#history .ope {
position:relative;
height:25px;
line-height:24px;
font-size:12px;
padding-left:15px;
overflow:auto;
}
#history .ope .amount {
width:50px;
text-align:right;
}
#history .ope .infos1 {
width:80px;
text-align:right;
}
#history .ope .infos2 {
padding-left:15px;
}
#history .ope .addcost {
padding-left:20px;
}
#history .ope .canceled {
padding-left:20px;
}
#history div.ope.ui-selected, #history div.ope.ui-selecting {
background-color:rgba(200,16,46,0.6);
color:#FFF;
}
#history .ope.canceled, #history .transfer.canceled {
color:#444;
}
#history .ope.canceled::before, #history.transfer.canceled::before {
position: absolute;
content: ' ';
width:100%;
left:0;
top: 12px;
border-top: 1px solid rgba(200,16,46,0.5);
}
#history .transfer .amount {
width:80px;
}
#history .transfer .from_acc {
padding-left:10px;
}

View file

@ -0,0 +1,265 @@
@import url("nav.css");
@import url("kpsul.css");
@import url("jconfirm-kfet.css");
@import url("history.css");
body {
margin-top:50px;
font-family:Roboto;
background:#ddd;
}
h1,h2,h3,h4,h5,h6 {
font-family:Oswald;
}
a {
color:#333;
}
a:focus, a:hover {
color:#C8102E;
}
:focus {
outline:none;
}
textarea {
font-family:'Roboto Mono';
border-radius:0 !important;
}
.table {
margin-bottom:0;
}
.table {
width:100%;
}
.table td {
vertical-align:middle !important;
}
.table td.no-padding {
padding:0;
}
.table thead {
background:#c8102e;
color:#fff;
font-weight:bold;
font-size:16px;
}
.table thead td {
padding:8px !important;
}
.table tr.section {
background:rgba(200,16,46,0.9);
color:#fff;
font-weight:bold;
}
.table tr.section td {
border-top:0;
font-size:16px;
padding:8px 30px;
}
.btn, .btn-lg, .btn-group-lg>.btn {
border-radius:0;
}
.btn-primary {
font-family:Oswald;
background-color:rgba(200,16,46,0.9);
color:#FFF;
border:0;
}
.btn-primary:hover, .btn-primary.focus, .btn-primary:focus {
background-color:#000;
color:#FFF;
}
.row-page-header {
background-color:rgba(200,16,46,1);
color:#FFF;
border-bottom:3px solid #000;
}
.page-header {
border:0;
padding:0;
margin:15px 20px;
text-transform:uppercase;
font-weight:bold;
}
.col-content-left, .col-content-right {
padding:0;
}
.content-left-top {
background:#fff;
padding:10px 30px;
}
.content-left .buttons {
background:#fff;
}
.content-left .buttons .btn {
display:block;
}
.content-left-top.frozen-account {
background:#000FBA;
color:#fff;
}
.content-left .block {
padding-top:25px;
}
.content-left .block .line {
font-size:16px;
line-height:30px;
}
.content-left .line.line-big {
font-family:Oswald;
font-size:60px;
font-weight:bold;
text-align:center;
}
.content-left .line.line-bigsub {
font-size:25px;
font-weight:bold;
text-align:center;
}
.content-left .line.balance {
font-size:45px;
text-align:center;
}
.content-right {
margin:0 15px;
}
.content-right-block {
padding-bottom:5px;
position:relative;
}
.content-right-block:last-child {
padding-bottom:15px;
}
.content-right-block > div:not(.buttons-title) {
background:#fff;
}
.content-right-block .buttons-title {
position:absolute;
top:8px;
right:20px;
}
.content-right-block > div.row {
margin:0;
}
.content-right-block h2 {
margin:20px 20px 15px;
padding-bottom:5px;
border-bottom:3px solid #c8102e;
font-size:40px;
}
.content-right-block h3 {
border-bottom: 1px solid #c8102e;
margin: 20px 15px 15px;
padding-bottom: 10px;
padding-left: 20px;
font-size:25px;
}
/*
* Pages formulaires seuls
*/
.form-only .content-form {
margin:15px;
background:#fff;
padding:15px;
}
.form-only .account_create #id_trigramme {
display:block;
width:200px;
height:80px;
margin:0 auto 15px;
border:1px solid #ccc;
font-size:70px;
text-align:center;
text-transform:uppercase;
}
/*
* Specific account create
*/
.highlight_autocomplete {
font-weight:bold;
text-decoration:underline;
}
#search_autocomplete {
margin-bottom:15px;
}
#search_results {
top:-15px !important;
left:0 !important;
z-index:100;
}
#search_results ul {
list-style-type:none;
padding:0;
background:rgba(255,255,255,0.9);
}
#search_results ul li.user_category {
font-weight:bold;
background:#c8102e;
color:#fff;
}
#search_results ul li a {
display:block;
padding:5px 20px;
height:100%;
width:100%;
}
#search_results ul li a:hover {
background:rgba(200,16,46,0.9);
color:#fff;
text-decoration:none;
}
#search_results ul li span.text {
display:block;
padding:5px 20px;
}

View file

@ -0,0 +1,66 @@
.jconfirm-bg.seen {
opacity:1 !important;
}
.jconfirm .jconfirm-box {
padding:0;
border-radius:0 !important;
font-family:"Roboto Mono";
}
.jconfirm .jconfirm-box div.title-c .title {
display: block;
padding:0 15px;
height:40px;
line-height:40px;
font-size:20px;
font-weight:bold;
color:#FFF;
background-color:#C8102E;
}
.jconfirm .jconfirm-box .content-pane {
margin:0 !important;
}
.jconfirm .jconfirm-box .content {
border-bottom:1px solid #ddd;
}
.jconfirm .jconfirm-box input {
width:100%;
height:70px;
border:0;
font-size:40px;
text-align:center;
}
.jconfirm .jconfirm-box .buttons {
margin-top:-5px; /* j'arrive pas à voir pk y'a un espace au dessus sinon... */
padding:0;
height:40px;
}
.jconfirm .jconfirm-box .buttons button {
height:100%;
margin:0;
margin:0 !important;
}
.jconfirm .jconfirm-box .buttons button:first-child:focus,
.jconfirm .jconfirm-box .buttons button:first-child:hover {
color:#FFF !important;
background:#009011 !important;
}
.jconfirm .jconfirm-box .buttons button:nth-child(2):focus,
.jconfirm .jconfirm-box .buttons button:nth-child(2):hover {
color:#FFF !important;
background:#C8102E !important;
}

View file

@ -0,0 +1,519 @@
/*!
* jquery-confirm v2.5.1 (http://craftpip.github.io/jquery-confirm/)
* Author: boniface pereira
* Website: www.craftpip.com
* Contact: hey@craftpip.com
*
* Copyright 2013-2016 jquery-confirm
* Licensed under MIT (https://github.com/craftpip/jquery-confirm/blob/master/LICENSE)
*/
@-webkit-keyframes jconfirm-rotate {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes jconfirm-rotate {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
body.jconfirm-noscroll {
overflow: hidden !important;
}
.jconfirm {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999999;
font-family: inherit;
overflow: hidden;
}
.jconfirm .jconfirm-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
-webkit-transition: all .4s;
transition: all .4s;
}
.jconfirm .jconfirm-bg.seen {
opacity: 1;
}
.jconfirm .jconfirm-scrollpane {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
-webkit-perspective: 500px;
perspective: 500px;
-webkit-perspective-origin: center;
perspective-origin: center;
}
.jconfirm .jconfirm-box {
background: white;
border-radius: 4px;
position: relative;
outline: none;
padding: 15px 15px 0;
}
.jconfirm .jconfirm-box div.closeIcon {
height: 20px;
width: 20px;
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
opacity: .6;
text-align: center;
-webkit-transition: opacity 0.1s ease-in;
transition: opacity 0.1s ease-in;
display: none;
font-size: 27px;
line-height: 14px;
}
.jconfirm .jconfirm-box div.closeIcon .fa {
font-size: 16px;
}
.jconfirm .jconfirm-box div.closeIcon .glyphicon {
font-size: 16px;
}
.jconfirm .jconfirm-box div.closeIcon .zmdi {
font-size: 16px;
}
.jconfirm .jconfirm-box div.closeIcon:hover {
opacity: 1;
}
.jconfirm .jconfirm-box div.title-c {
display: block;
font-size: 22px;
line-height: 20px;
}
.jconfirm .jconfirm-box div.title-c .icon-c {
font-size: inherit;
padding-bottom: 15px;
display: inline-block;
margin-right: 8px;
vertical-align: middle;
}
.jconfirm .jconfirm-box div.title-c .icon-c i {
vertical-align: middle;
}
.jconfirm .jconfirm-box div.title-c .icon-c:empty {
display: none;
}
.jconfirm .jconfirm-box div.title-c .title {
font-size: inherit;
font-family: inherit;
display: inline-block;
vertical-align: middle;
padding-bottom: 15px;
}
.jconfirm .jconfirm-box div.title-c .title:empty {
display: none;
}
.jconfirm .jconfirm-box div.content-pane {
margin-bottom: 15px;
height: auto;
-webkit-transition: height 0.4s ease-in;
transition: height 0.4s ease-in;
display: inline-block;
width: 100%;
position: relative;
}
.jconfirm .jconfirm-box div.content-pane .content {
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 0.2s ease-in;
transition: all 0.2s ease-in;
right: 0;
}
.jconfirm .jconfirm-box div.content-pane .content img {
width: 100%;
height: auto;
}
.jconfirm .jconfirm-box div.content-pane .content:empty {
display: none;
}
.jconfirm .jconfirm-box div.content-pane .content:empty.loading {
height: 40px;
position: relative;
opacity: 0.6;
display: block;
}
.jconfirm .jconfirm-box div.content-pane .content:empty.loading:before {
content: '';
height: 20px;
width: 20px;
border: solid 2px transparent;
position: absolute;
left: 50%;
margin-left: -10px;
border-radius: 50%;
-webkit-animation: jconfirm-rotate 1s infinite linear;
animation: jconfirm-rotate 1s infinite linear;
border-bottom-color: #aaa;
top: 50%;
margin-top: -10px;
}
.jconfirm .jconfirm-box div.content-pane .content:empty.loading:after {
content: '';
position: absolute;
left: 50%;
margin-left: -15px;
}
.jconfirm .jconfirm-box .buttons {
padding-bottom: 15px;
}
.jconfirm .jconfirm-box .buttons button + button {
margin-left: 5px;
}
.jconfirm .jquery-clear {
clear: both;
}
.jconfirm.rtl {
direction: rtl;
}
.jconfirm.rtl div.closeIcon {
left: 12px;
right: auto;
}
.jconfirm.jconfirm-white .jconfirm-bg {
background-color: rgba(0, 0, 0, 0.2);
}
.jconfirm.jconfirm-white .jconfirm-box {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
border-radius: 5px;
}
.jconfirm.jconfirm-white .jconfirm-box .buttons {
float: right;
}
.jconfirm.jconfirm-white .jconfirm-box .buttons button {
border: none;
background-image: none;
text-transform: uppercase;
font-size: 14px;
font-weight: bold;
text-shadow: none;
-webkit-transition: background .1s;
transition: background .1s;
color: white;
}
.jconfirm.jconfirm-white .jconfirm-box .buttons button.btn-default {
box-shadow: none;
color: #333;
}
.jconfirm.jconfirm-white .jconfirm-box .buttons button.btn-default:hover {
background: #ddd;
}
.jconfirm.jconfirm-black .jconfirm-bg {
background-color: rgba(0, 0, 0, 0.5);
}
.jconfirm.jconfirm-black .jconfirm-box {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
background: #444;
border-radius: 5px;
color: white;
}
.jconfirm.jconfirm-black .jconfirm-box .buttons {
float: right;
}
.jconfirm.jconfirm-black .jconfirm-box .buttons button {
border: none;
background-image: none;
text-transform: uppercase;
font-size: 14px;
font-weight: bold;
text-shadow: none;
-webkit-transition: background .1s;
transition: background .1s;
color: white;
}
.jconfirm.jconfirm-black .jconfirm-box .buttons button.btn-default {
box-shadow: none;
color: #fff;
background: none;
}
.jconfirm.jconfirm-black .jconfirm-box .buttons button.btn-default:hover {
background: #666;
}
.jconfirm .jconfirm-box.hilight {
-webkit-animation: hilight 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
animation: hilight 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
@-webkit-keyframes hilight {
10%,
90% {
-webkit-transform: translate3d(-2px, 0, 0);
transform: translate3d(-2px, 0, 0);
}
20%,
80% {
-webkit-transform: translate3d(4px, 0, 0);
transform: translate3d(4px, 0, 0);
}
30%,
50%,
70% {
-webkit-transform: translate3d(-8px, 0, 0);
transform: translate3d(-8px, 0, 0);
}
40%,
60% {
-webkit-transform: translate3d(8px, 0, 0);
transform: translate3d(8px, 0, 0);
}
}
@keyframes hilight {
10%,
90% {
-webkit-transform: translate3d(-2px, 0, 0);
transform: translate3d(-2px, 0, 0);
}
20%,
80% {
-webkit-transform: translate3d(4px, 0, 0);
transform: translate3d(4px, 0, 0);
}
30%,
50%,
70% {
-webkit-transform: translate3d(-8px, 0, 0);
transform: translate3d(-8px, 0, 0);
}
40%,
60% {
-webkit-transform: translate3d(8px, 0, 0);
transform: translate3d(8px, 0, 0);
}
}
/*Transition rules*/
.jconfirm {
-webkit-perspective: 400px;
perspective: 400px;
}
.jconfirm .jconfirm-box {
opacity: 1;
-webkit-transition-property: -webkit-transform, opacity, box-shadow;
transition-property: transform, opacity, box-shadow;
}
.jconfirm .jconfirm-box.anim-top,
.jconfirm .jconfirm-box.anim-left,
.jconfirm .jconfirm-box.anim-right,
.jconfirm .jconfirm-box.anim-bottom,
.jconfirm .jconfirm-box.anim-opacity,
.jconfirm .jconfirm-box.anim-zoom,
.jconfirm .jconfirm-box.anim-scale,
.jconfirm .jconfirm-box.anim-none,
.jconfirm .jconfirm-box.anim-rotate,
.jconfirm .jconfirm-box.anim-rotatex,
.jconfirm .jconfirm-box.anim-rotatey,
.jconfirm .jconfirm-box.anim-scaley,
.jconfirm .jconfirm-box.anim-scalex {
opacity: 0;
}
.jconfirm .jconfirm-box.anim-rotate {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.jconfirm .jconfirm-box.anim-rotatex {
-webkit-transform: rotateX(90deg);
transform: rotateX(90deg);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-rotatexr {
-webkit-transform: rotateX(-90deg);
transform: rotateX(-90deg);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-rotatey {
-webkit-transform: rotatey(90deg);
-ms-transform: rotatey(90deg);
transform: rotatey(90deg);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-rotateyr {
-webkit-transform: rotatey(-90deg);
-ms-transform: rotatey(-90deg);
transform: rotatey(-90deg);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-scaley {
-webkit-transform: scaley(1.5);
-ms-transform: scaley(1.5);
transform: scaley(1.5);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-scalex {
-webkit-transform: scalex(1.5);
-ms-transform: scalex(1.5);
transform: scalex(1.5);
-webkit-transform-origin: center;
-ms-transform-origin: center;
transform-origin: center;
}
.jconfirm .jconfirm-box.anim-top {
-webkit-transform: translate(0px, -100px);
-ms-transform: translate(0px, -100px);
transform: translate(0px, -100px);
}
.jconfirm .jconfirm-box.anim-left {
-webkit-transform: translate(-100px, 0px);
-ms-transform: translate(-100px, 0px);
transform: translate(-100px, 0px);
}
.jconfirm .jconfirm-box.anim-right {
-webkit-transform: translate(100px, 0px);
-ms-transform: translate(100px, 0px);
transform: translate(100px, 0px);
}
.jconfirm .jconfirm-box.anim-bottom {
-webkit-transform: translate(0px, 100px);
-ms-transform: translate(0px, 100px);
transform: translate(0px, 100px);
}
.jconfirm .jconfirm-box.anim-zoom {
-webkit-transform: scale(1.2);
-ms-transform: scale(1.2);
transform: scale(1.2);
}
.jconfirm .jconfirm-box.anim-scale {
-webkit-transform: scale(0.5);
-ms-transform: scale(0.5);
transform: scale(0.5);
}
.jconfirm .jconfirm-box.anim-none {
display: none;
}
.jconfirm.jconfirm-supervan .jconfirm-bg {
background-color: rgba(54, 70, 93, 0.95);
}
.jconfirm.jconfirm-supervan .jconfirm-box {
background-color: transparent;
}
.jconfirm.jconfirm-supervan .jconfirm-box div.closeIcon {
color: white;
}
.jconfirm.jconfirm-supervan .jconfirm-box div.title-c {
text-align: center;
color: white;
font-size: 28px;
font-weight: normal;
}
.jconfirm.jconfirm-supervan .jconfirm-box div.title-c > * {
padding-bottom: 25px;
}
.jconfirm.jconfirm-supervan .jconfirm-box div.content-pane {
margin-bottom: 25px;
}
.jconfirm.jconfirm-supervan .jconfirm-box div.content {
text-align: center;
color: white;
}
.jconfirm.jconfirm-supervan .jconfirm-box .buttons {
text-align: center;
}
.jconfirm.jconfirm-supervan .jconfirm-box .buttons button {
font-size: 16px;
border-radius: 2px;
background: #303f53;
text-shadow: none;
border: none;
color: white;
padding: 10px;
min-width: 100px;
}
.jconfirm.jconfirm-material .jconfirm-bg {
background-color: rgba(0, 0, 0, 0.67);
}
.jconfirm.jconfirm-material .jconfirm-box {
background-color: white;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12);
padding: 30px 25px 10px 25px;
}
.jconfirm.jconfirm-material .jconfirm-box div.closeIcon {
color: rgba(0, 0, 0, 0.87);
}
.jconfirm.jconfirm-material .jconfirm-box div.title-c {
color: rgba(0, 0, 0, 0.87);
font-size: 22px;
font-weight: bold;
}
.jconfirm.jconfirm-material .jconfirm-box div.content {
text-align: left;
color: rgba(0, 0, 0, 0.87);
}
.jconfirm.jconfirm-material .jconfirm-box .buttons {
text-align: right;
}
.jconfirm.jconfirm-material .jconfirm-box .buttons button {
text-transform: uppercase;
font-weight: 500;
}
.jconfirm.jconfirm-bootstrap .jconfirm-bg {
background-color: rgba(0, 0, 0, 0.21);
}
.jconfirm.jconfirm-bootstrap .jconfirm-box {
background-color: white;
box-shadow: 0 3px 8px 0px rgba(0, 0, 0, 0.2);
border: solid 1px rgba(0, 0, 0, 0.4);
padding: 15px 0 0;
}
.jconfirm.jconfirm-bootstrap .jconfirm-box div.closeIcon {
color: rgba(0, 0, 0, 0.87);
}
.jconfirm.jconfirm-bootstrap .jconfirm-box div.title-c {
color: rgba(0, 0, 0, 0.87);
font-size: 22px;
font-weight: bold;
padding-left: 15px;
padding-right: 15px;
}
.jconfirm.jconfirm-bootstrap .jconfirm-box div.content {
text-align: left;
color: rgba(0, 0, 0, 0.87);
padding: 0px 15px;
}
.jconfirm.jconfirm-bootstrap .jconfirm-box .buttons {
text-align: right;
padding: 0px 0 0px;
margin: -5px 0 0px;
border-top: solid 1px #ddd;
overflow: hidden;
border-radius: 0 0 4px 4px;
}
.jconfirm.jconfirm-bootstrap .jconfirm-box .buttons button {
font-weight: 500;
border-radius: 0px;
margin: 0;
border-left: solid 1px #ddd;
}

View file

@ -0,0 +1,6 @@
/*! jQuery UI - v1.12.0 - 2016-08-17
* http://jqueryui.com
* Includes: selectable.css
* Copyright jQuery Foundation and other contributors; Licensed MIT */
.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}

View file

@ -0,0 +1,385 @@
input[type=number] {
-moz-appearance:textfield;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
#account, #checkout, input, #history, #basket, #basket_rel, #articles_data {
background:#fff;
}
@keyframes colorchange {
0% { background: yellow; }
50% { background: #660066; }
100% { background: yellow; }
}
/*
* Top row
*/
.row.kpsul_top {
padding:0 15px;
color:#000;
}
.row.kpsul_top > div {
margin-top:15px;
}
/* Account */
#account {
color:black;
height:120px;
}
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account_form {
padding:0;
height:100%;
}
#account_form input {
width:100%;
height:100%;
padding:0;
padding-bottom:10px;
border:0;
border-radius:0;
background:#f3f3f3;
font-family:'Roboto Mono';
font-size:50px;
font-weight:bold;
text-align:center;
text-transform:uppercase;
}
#account_data {
height:100%;
}
#account_data .data_line {
line-height:16px;
font-family:'Roboto Mono';
font-size:12px;
}
#account_data #account-balance {
height:40px;
line-height:40px;
font-size:35px;
font-weight:bold;
}
#account-name {
font-weight:bold;
}
#account .buttons {
position:absolute;
bottom:0;
right:0;
}
@media (min-width: 600px) {
#account_form input { font-size:60px; }
@media (min-width: 768px) {
#account{ height:160px; }
#account_form input { font-size: 70px; }
#account_name { font-size:60px; }
#account_data .data_line {
font-size:14px;
line-height:24px;
}
#account_data #account-balance {
font-size:50px;
line-height:60px;
height:60px;
}
#account { margin-right:0; }
#account_form input { font-size:85px; }
@media (min-width: 992px) {
#account_form input { font-size:100px; }
}
}
}
/* Checkout */
#checkout {
padding:0;
height:160px;
font-family:'Roboto Mono';
}
#checkout_form select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor:pointer;
outline:none;
border:0;
width:100%;
height:50px;
padding:0 15px;
background:#c8102e;
color:#fff;
font-weight:bold;
font-size:18px;
}
#checkout_form select option {
height:25px;
padding:0 15px;
line-height:25px;
font-weight:normal;
font-size:14px;
background-color:rgba(255,255,255,0.1);
}
#checkout_data {
padding:0 15px;
}
#checkout_data .buttons {
position:absolute;
bottom:0;
right:0;
}
#addcost {
position:absolute;
bottom:0;
left:0;
height:32px;
line-height:32px;
padding:0 10px;
background:#c8102e;
color:#fff;
font-weight:bold;
animation: blink 1.5s steps(1) infinite;
}
@keyframes blink { 50% { visibility: hidden; } }
/*
* Second part
*/
.row.kpsul_middle {
margin:15px 0;
font-family:'Roboto Mono';
color:#000;
}
.row.kpsul_middle > div {
padding:0;
height:100%;
}
.row.kpsul_middle > div:first-child > div {
margin-right:0;
}
.row.kpsul_middle > div+div {
margin-top:15px;
}
@media (min-width:768px) {
.row.kpsul_middle > div:first-child > div {
margin-right:15px
}
.row.kpsul_middle > div {
margin-top:0 !important;
}
}
/* Special operations */
#special_operations {
height:40px;
}
#special_operations button {
height:100%;
width:25%;
float:left;
background:#c8102e;
color:#FFF;
font-size:18px;
font-weight:bold;
}
#special_operations button:focus, #special_operations button:hover {
outline:none;
background:#000;
color:#fff;
}
/* Article autocomplete */
#article_selection {
height:40px;
width:100%;
}
#article_selection input {
height:100%;
float:left;
border:0;
border-right:1px solid #c8102e;
border-bottom:1px solid #c8102e;
border-radius:0;
font-size:16px;
font-weight:bold;
}
#article_selection input+input {
border-right:0;
}
#article_autocomplete {
width:90%;
padding-left:10px;
}
#article_number {
width:10%;
text-align:center;
}
@media (min-width:1200px) {
#article_autocomplete {
width:92%
}
#article_number {
width:8%;
}
}
/* Article data */
#articles_data {
overflow:auto;
max-height:500px;
}
#articles_data table {
width: 100%;
}
#articles_data table tr.article {
height:25px;
font-size:14px;
}
#articles_data table tr.article>td:first-child {
padding-left:10px;
}
#articles_data table tr.category {
height:35px;
background-color:#c8102e;
font-size:16px;
color:#FFF;
font-weight:bold;
}
#articles_data table tr.category>td:first-child {
padding-left:20px;
}
/* Second part - Left - bottom */
.kpsul_middle_left_bottom {
margin:15px 0 0;
}
.kpsul_middle_left_bottom > div {
height:160px;
padding:0;
}
#basket, #basket_rel {
height:100%;
}
#basket_rel {
border-top:1px solid #C8102E;
}
#basket {
overflow:auto;
}
@media (min-width:768px) {
#basket {
margin-right:7px;
}
#basket_rel {
border-top:0;
margin-left:7px;
}
}
#basket table {
width:100%;
}
#basket table tr {
height:25px;
font-size:14px;
}
#basket tr .amount {
width:70px;
padding-right:15px;
text-align:right;
}
#basket tr .number {
width:50px;
padding-right:15px;
text-align:right;
}
#basket tr.ui-selected, #basket tr.ui-selecting {
background-color:rgba(200,16,46,0.6);
color:#FFF;
}
/* History */
.kpsul_middle_right_col {
overflow:auto;
}

View file

@ -0,0 +1,9 @@
@media (min-width:768px) {
html { height:100%; }
body { height:calc(100% - 50px); }
.container-fluid { height:100%; }
.kpsul_middle { height:calc(100% - 205px); }
.kpsul_middle_left_top { height:calc(100% - 175px); min-height:80px; }
.kpsul_middle_left { height:100%; }
#articles_data { height: calc(100% - 80px); }
}

View file

@ -0,0 +1,191 @@
/**
* @author zhixin wen <wenzhixin2010@gmail.com>
*/
.ms-parent {
display: inline-block;
position: relative;
vertical-align: middle;
}
.ms-choice {
display: block;
width: 100%;
height: 26px;
padding: 0;
overflow: hidden;
cursor: pointer;
border: 1px solid #aaa;
text-align: left;
white-space: nowrap;
line-height: 26px;
color: #444;
text-decoration: none;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
background-color: #fff;
}
.ms-choice.disabled {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.ms-choice > span {
position: absolute;
top: 0;
left: 0;
right: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
padding-left: 8px;
}
.ms-choice > span.placeholder {
color: #999;
}
.ms-choice > div {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 25px;
background: url('../img/multiple-select.png') left top no-repeat;
}
.ms-choice > div.open {
background: url('../img/multiple-select.png') right top no-repeat;
}
.ms-drop {
width: 100%;
overflow: hidden;
display: none;
margin-top: -1px;
padding: 0;
position: absolute;
z-index: 1000;
background: #fff;
color: #000;
border: 1px solid #aaa;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.ms-drop.bottom {
top: 100%;
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
-moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
}
.ms-drop.top {
bottom: 100%;
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
-moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
}
.ms-search {
display: inline-block;
margin: 0;
min-height: 26px;
padding: 4px;
position: relative;
white-space: nowrap;
width: 100%;
z-index: 10000;
}
.ms-search input {
width: 100%;
height: auto !important;
min-height: 24px;
padding: 0 20px 0 5px;
margin: 0;
outline: 0;
font-family: sans-serif;
font-size: 1em;
border: 1px solid #aaa;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
background: #fff url('../img/multiple-select.png') no-repeat 100% -22px;
background: url('../img/multiple-select.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background: url('../img/multiple-select.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../img/multiple-select.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('../img/multiple-select.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background: url('../img/multiple-select.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
background: url('../img/multiple-select.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
}
.ms-search, .ms-search input {
-webkit-box-sizing: border-box;
-khtml-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.ms-drop ul {
overflow: auto;
margin: 0;
padding: 5px 8px;
}
.ms-drop ul > li {
list-style: none;
display: list-item;
background-image: none;
position: static;
}
.ms-drop ul > li .disabled {
opacity: .35;
filter: Alpha(Opacity=35);
}
.ms-drop ul > li.multiple {
display: block;
float: left;
}
.ms-drop ul > li.group {
clear: both;
}
.ms-drop ul > li.multiple label {
width: 100%;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ms-drop ul > li label {
font-weight: normal;
display: block;
white-space: nowrap;
}
.ms-drop ul > li label.optgroup {
font-weight: bold;
}
.ms-drop input[type="checkbox"] {
vertical-align: middle;
}
.ms-drop .ms-no-results {
display: none;
}

View file

@ -0,0 +1,67 @@
nav {
background:#000;
color:#DDD;
font-family:Oswald;
}
.navbar-nav > li > .dropdown-menu {
border:0;
border-radius:0;
}
.navbar-fixed-top {
border:0;
}
nav .navbar-brand {
padding:3px 25px;
}
nav .navbar-brand img {
height:44px;
}
nav .navbar-toggle .icon-bar {
background-color:#FFF;
}
nav a {
color:#DDD;
}
.navbar-nav {
font-weight:bold;
font-size:14px;
text-transform:uppercase;
}
.nav>li>a:focus, .nav>li>a:hover {
background-color:#C8102E;
color:#FFF;
}
.nav .open>a, .nav .open>a:focus, .nav .open>a:hover {
background-color:#C8102E;
}
.dropdown-menu {
padding:0;
}
.dropdown-menu>li>a {
padding:8px 20px;
}
.dropdown-menu .divider {
margin:0;
}
@media (max-width: 767px) {
.navbar-nav .open .dropdown-menu {
background-color:#FFF;
}
.navbar-nav {
margin:0 -15px;
}
}

View file

@ -0,0 +1,73 @@
.transfer_general {
margin:15px 0;
height:45px;
}
.transfer_general input {
height:100%;
border:0;
padding:0 5px;
font-size:16px;
}
.transfer_general button {
height:100%;
margin-top:-3px;
}
.transfer_formset {
background:#FFF;
}
.transfer_formset thead {
height:40px;
background:#c8102e;
color:#fff;
font-size:20px;
font-weight:bold;
text-align:center;
}
.transfer_form td {
height:50px;
vertical-align:middle !important;
padding:0 !important;
}
.transfer_form input {
border:0;
border-radius:0;
width:100%;
height:100%;
font-family:'Roboto Mono';
font-size:25px;
font-weight:bold;
text-align:center;
vertical-align:middle;
text-transform:uppercase;
}
.transfer_form .from_acc_data, .transfer_form .to_acc_data {
width:30%;
text-align:center;
font-size:20px;
}
.transfer_form .from_acc, .transfer_form .to_acc {
width:15%;
}
.transfer_form .from_acc {
border-left:1px solid #ddd;
}
.transfer_form .to_acc {
border-right:1px solid #ddd;
}
.transfer_form .amount {
width:10%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,139 @@
function KHistory(options={}) {
$.extend(this, KHistory.default_options, options);
this.$container = $(this.container);
this.reset = function() {
this.$container.html('');
};
this.addOpeGroup = function(opegroup) {
var $day = this._getOrCreateDay(opegroup['at']);
var $opegroup = this._opeGroupHtml(opegroup);
$day.after($opegroup);
var trigramme = opegroup['on_acc_trigramme'];
var is_cof = opegroup['is_cof'];
for (var i=0; i<opegroup['opes'].length; i++) {
var $ope = this._opeHtml(opegroup['opes'][i], is_cof, trigramme);
$ope.data('opegroup', opegroup['id']);
$opegroup.after($ope);
}
}
this._opeHtml = function(ope, is_cof, trigramme) {
var $ope_html = $(this.template_ope);
var parsed_amount = parseFloat(ope['amount']);
var amount = amountDisplay(parsed_amount, is_cof, trigramme);
var infos1 = '', infos2 = '';
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';
}
$ope_html
.data('ope', ope['id'])
.find('.amount').text(amount).end()
.find('.infos1').text(infos1).end()
.find('.infos2').text(infos2).end();
var addcost_for = ope['addcost_for__trigramme'];
if (addcost_for) {
var addcost_amount = parseFloat(ope['addcost_amount']);
$ope_html.find('.addcost').text('('+amountDisplay(addcost_amount, is_cof)+'UKF pour '+addcost_for+')');
}
if (ope['canceled_at'])
this.cancelOpe(ope, $ope_html);
return $ope_html;
}
this.cancelOpe = function(ope, $ope = null) {
if (!$ope)
$ope = this.findOpe(ope['id']);
var cancel = 'Annulé';
var canceled_at = dateUTCToParis(ope['canceled_at']);
if (ope['canceled_by__trigramme'])
cancel += ' par '+ope['canceled_by__trigramme'];
cancel += ' le '+canceled_at.format('DD/MM/YY à HH:mm:ss');
$ope.addClass('canceled').find('.canceled').text(cancel);
}
this._opeGroupHtml = function(opegroup) {
var $opegroup_html = $(this.template_opegroup);
var at = dateUTCToParis(opegroup['at']).format('HH:mm:ss');
var trigramme = opegroup['on_acc__trigramme'];
var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
var comment = opegroup['comment'] || '';
$opegroup_html
.data('opegroup', opegroup['id'])
.find('.time').text(at).end()
.find('.amount').text(amount).end()
.find('.comment').text(comment).end()
.find('.trigramme').text(trigramme).end();
if (!this.display_trigramme)
$opegroup_html.find('.trigramme').remove();
if (opegroup['valid_by__trigramme'])
$opegroup_html.find('.valid_by').text('Par '+opegroup['valid_by__trigramme']);
return $opegroup_html;
}
this._getOrCreateDay = function(date) {
var at = dateUTCToParis(date);
var at_ser = at.format('YYYY-MM-DD');
var $day = this.$container.find('.day').filter(function() {
return $(this).data('date') == at_ser
});
if ($day.length == 1)
return $day;
var $day = $(this.template_day).prependTo(this.$container);
return $day.data('date', at_ser).text(at.format('D MMMM'));
}
this.findOpeGroup = function(id) {
return this.$container.find('.opegroup').filter(function() {
return $(this).data('opegroup') == id
});
}
this.findOpe = function(id) {
return this.$container.find('.ope').filter(function() {
return $(this).data('ope') == id
});
}
this.cancelOpeGroup = function(opegroup) {
var $opegroup = this.findOpeGroup(opegroup['id']);
var trigramme = $opegroup.find('.trigramme').text();
var amount = amountDisplay(
parseFloat(opegroup['amount'], opegroup['is_cof'], trigramme));
$opegroup.find('.amount').text(amount);
}
}
KHistory.default_options = {
container: '#history',
template_day: '<div class="day"></div>',
template_opegroup: '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
template_ope: '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
display_trigramme: true,
}

607
kfet/static/kfet/js/jquery-confirm.js vendored Normal file
View file

@ -0,0 +1,607 @@
/*!
* jquery-confirm v2.5.1 (http://craftpip.github.io/jquery-confirm/)
* Author: Boniface Pereira
* Website: www.craftpip.com
* Contact: hey@craftpip.com
*
* Copyright 2013-2015 jquery-confirm
* Licensed under MIT (https://github.com/craftpip/jquery-confirm/blob/master/LICENSE)
*
* Modifié par: Aurélien Delobelle
* Contact: aure.delo@gmail.com
* Contenu des modifications:
* Cas il y a un input avec autofocus dans 'content' -> Fix
*/
if (typeof jQuery === 'undefined') {
throw new Error('jquery-confirm requires jQuery');
}
var jconfirm, Jconfirm;
(function ($) {
"use strict";
$.fn.confirm = function (options, option2) {
if (typeof options === 'undefined') options = {};
if (typeof options === 'string')
options = {
content: options,
title: (option2) ? option2 : false
};
/*
* Alias of $.confirm to emulate native confirm()
*/
$(this).each(function () {
var $this = $(this);
$this.on('click', function (e) {
e.preventDefault();
var jcOption = $.extend({}, options);
if ($this.attr('data-title'))
jcOption['title'] = $this.attr('data-title');
if ($this.attr('data-content'))
jcOption['content'] = $this.attr('data-content');
jcOption['$target'] = $this;
if ($this.attr('href') && !options['confirm'])
jcOption['confirm'] = function () {
location.href = $this.attr('href');
};
$.confirm(jcOption);
});
});
return $(this);
};
$.confirm = function (options, option2) {
if (typeof options === 'undefined') options = {};
if (typeof options === 'string')
options = {
content: options,
title: (option2) ? option2 : false
};
/*
* Alias of jconfirm
*/
return jconfirm(options);
};
$.alert = function (options, option2) {
if (typeof options === 'undefined') options = {};
if (typeof options === 'string')
options = {
content: options,
title: (option2) ? option2 : false
};
/*
* Alias of jconfirm
*/
options.cancelButton = false;
return jconfirm(options);
};
$.dialog = function (options, option2) {
if (typeof options === 'undefined') options = {};
if (typeof options === 'string')
options = {
content: options,
title: (option2) ? option2 : false
};
/*
* Alias of jconfirm
*/
options.cancelButton = false;
options.confirmButton = false;
options.confirmKeys = [13];
return jconfirm(options);
};
jconfirm = function (options) {
if (typeof options === 'undefined') options = {};
/*
* initial function for calling.
*/
if (jconfirm.defaults) {
/*
* Merge global defaults with plugin defaults
*/
$.extend(jconfirm.pluginDefaults, jconfirm.defaults);
}
/*
* merge options with plugin defaults.
*/
var options = $.extend({}, jconfirm.pluginDefaults, options);
return new Jconfirm(options);
};
Jconfirm = function (options) {
/*
* constructor function Jconfirm,
* options = user options.
*/
$.extend(this, options);
this._init();
};
Jconfirm.prototype = {
_init: function () {
var that = this;
this._rand = Math.round(Math.random() * 99999);
this._buildHTML();
this._bindEvents();
setTimeout(function () {
that.open();
that._watchContent();
}, 0);
},
_buildHTML: function () {
var that = this;
/*
* Prefixing animations.
*/
this.animation = 'anim-' + this.animation.toLowerCase();
this.closeAnimation = 'anim-' + this.closeAnimation.toLowerCase();
this.theme = 'jconfirm-' + this.theme.toLowerCase();
if (this.animation == 'anim-none')
this.animationSpeed = 0;
this._lastFocused = $('body').find(':focus');
/*
* Append html.
*/
this.$el = $(this.template).appendTo(this.container).addClass(this.theme);
this.$el.find('.jconfirm-box-container').addClass(this.columnClass);
this.$el.find('.jconfirm-bg').css(this._getCSS(this.animationSpeed, 1));
this.$el.find('.jconfirm-bg').css('opacity', this.opacity);
this.$b = this.$el.find('.jconfirm-box').css(this._getCSS(this.animationSpeed, this.animationBounce)).addClass(this.animation);
this.$body = this.$b; // alias
/*
* Add rtl class if rtl option has selected
*/
if (this.rtl)
this.$el.addClass("rtl");
this._contentReady = $.Deferred();
this._modalReady = $.Deferred();
/*
* Setup title contents
*/
this.$title = this.$el.find('.title');
this.contentDiv = this.$el.find('div.content');
this.$content = this.contentDiv; // alias
this.$contentPane = this.$el.find('.content-pane');
this.$icon = this.$el.find('.icon-c');
this.$closeIcon = this.$el.find('.closeIcon');
this.$contentPane.css(this._getCSS(this.animationSpeed, 1));
this.setTitle();
this.setIcon();
this._setButtons();
if (this.closeIconClass)
this.$closeIcon.html('<i class="' + this.closeIconClass + '"></i>');
that._contentHash = this._hash(that.$content.html());
$.when(this._contentReady, this._modalReady).then(function () {
that.setContent();
that.setTitle();
that.setIcon();
});
this._getContent();
this._imagesLoaded();
if (this.autoClose)
this._startCountDown();
},
_unwatchContent: function () {
clearInterval(this._timer);
},
_hash: function () {
return btoa((encodeURIComponent(this.$content.html())));
},
_watchContent: function () {
var that = this;
this._timer = setInterval(function () {
var now = that._hash(that.$content.html());
if (that._contentHash != now) {
that._contentHash = now;
that.setDialogCenter();
that._imagesLoaded();
}
}, this.watchInterval);
},
_bindEvents: function () {
var that = this;
var boxClicked = false;
this.$el.find('.jconfirm-scrollpane').click(function (e) {
// ignore propagated clicks
if (!boxClicked) {
// background clicked
if (that.backgroundDismiss) {
that.cancel();
that.close();
} else {
that.$b.addClass('hilight');
setTimeout(function () {
that.$b.removeClass('hilight');
}, 800);
}
}
boxClicked = false;
});
this.$el.find('.jconfirm-box').click(function (e) {
boxClicked = true;
});
if (this.$confirmButton) {
this.$confirmButton.click(function (e) {
e.preventDefault();
var r = that.confirm(that.$b);
that._stopCountDown();
that.onAction('confirm');
if (typeof r === 'undefined' || r)
that.close();
});
}
if (this.$cancelButton) {
this.$cancelButton.click(function (e) {
e.preventDefault();
var r = that.cancel(that.$b);
that._stopCountDown();
that.onAction('cancel');
if (typeof r === 'undefined' || r)
that.close();
});
}
if (this.$closeButton) {
this.$closeButton.click(function (e) {
e.preventDefault();
that._stopCountDown();
that.cancel();
that.onAction('close');
that.close();
});
}
if (this.keyboardEnabled) {
setTimeout(function () {
$(window).on('keyup.' + this._rand, function (e) {
that.reactOnKey(e);
});
}, 500);
}
$(window).on('resize.' + this._rand, function () {
that.setDialogCenter(true);
});
},
_getCSS: function (speed, bounce) {
return {
'-webkit-transition-duration': speed / 1000 + 's',
'transition-duration': speed / 1000 + 's',
'-webkit-transition-timing-function': 'cubic-bezier(.36,1.1,.2, ' + bounce + ')',
'transition-timing-function': 'cubic-bezier(.36,1.1,.2, ' + bounce + ')'
};
},
_imagesLoaded: function () {
var that = this;
$.each(this.$content.find('img:not(.loaded)'), function (i, a) {
var interval = setInterval(function () {
var h = $(a).css('height');
if (h !== '0px') {
$(a).addClass('loaded');
that.setDialogCenter();
clearInterval(interval);
}
}, 40);
})
},
_setButtons: function () {
/*
* Settings up buttons
*/
this.$btnc = this.$el.find('.buttons');
if (this.confirmButton && $.trim(this.confirmButton) !== '') {
this.$confirmButton = $('<button type="button" class="btn">' + this.confirmButton + '</button>').appendTo(this.$btnc).addClass(this.confirmButtonClass);
}
if (this.cancelButton && $.trim(this.cancelButton) !== '') {
this.$cancelButton = $('<button type="button" class="btn">' + this.cancelButton + '</button>').appendTo(this.$btnc).addClass(this.cancelButtonClass);
}
if (!this.confirmButton && !this.cancelButton) {
this.$btnc.hide();
}
if (!this.confirmButton && !this.cancelButton && this.closeIcon === null) {
this.$closeButton = this.$b.find('.closeIcon').show();
}
if (this.closeIcon === true) {
this.$closeButton = this.$b.find('.closeIcon').show();
}
},
setTitle: function (string) {
this.title = (typeof string !== 'undefined') ? string : this.title;
this.$title.html(this.title || '');
},
setIcon: function (iconClass) {
this.title = (typeof string !== 'undefined') ? iconClass : this.title;
this.$icon.html(this.icon ? '<i class="' + this.icon + '"></i>' : '');
},
setContent: function (string) {
// only set the content on the modal.
var that = this;
this.content = (typeof string == 'undefined') ? this.content : string;
if (this.content == '') {
this.$content.html(this.content);
this.$contentPane.hide();
} else {
this.$content.html(this.content);
this.$contentPane.show();
}
if (this.$content.hasClass('loading')) {
this.$content.removeClass('loading');// it was loading via ajax.
this.$btnc.find('button').prop('disabled', false);
}
},
_getContent: function (string) {
// get content from remote & stuff.
var that = this;
string = (string) ? string : this.content;
this._isAjax = false;
/*
* Set content.
*/
if (!this.content) { // if the content is falsy
this.content = '';
this.setContent(this.content);
this._contentReady.reject();
} else if (typeof this.content === 'string') {
if (this.content.substr(0, 4).toLowerCase() === 'url:') {
this._isAjax = true;
this.$content.addClass('loading');
this.$btnc.find('button').prop('disabled', true);
var url = this.content.substring(4, this.content.length);
$.get(url).done(function (html) {
that.content = html;
that._contentReady.resolve();
}).always(function (data, status, xhr) {
if (typeof that.contentLoaded === 'function')
that.contentLoaded(data, status, xhr);
});
} else {
this.setContent(this.content);
this._contentReady.reject();
}
} else if (typeof this.content === 'function') {
this.$content.addClass('loading');
this.$btnc.find('button').attr('disabled', 'disabled');
var promise = this.content(this);
if (typeof promise !== 'object') {
console.error('The content function must return jquery promise.');
} else if (typeof promise.always !== 'function') {
console.error('The object returned is not a jquery promise.');
} else {
this._isAjax = true;
promise.always(function (data, status) {
that._contentReady.resolve();
});
}
} else {
console.error('Invalid option for property content, passed: ' + typeof this.content);
}
this.setDialogCenter();
},
_stopCountDown: function () {
clearInterval(this.timerInterval);
if (this.$cd)
this.$cd.remove();
},
_startCountDown: function () {
var opt = this.autoClose.split('|');
if (/cancel/.test(opt[0]) && this.type === 'alert') {
return false;
} else if (/confirm|cancel/.test(opt[0])) {
this.$cd = $('<span class="countdown">').appendTo(this['$' + opt[0] + 'Button']);
var that = this;
that.$cd.parent().click();
var time = opt[1] / 1000;
this.timerInterval = setInterval(function () {
that.$cd.html(' (' + (time -= 1) + ')');
if (time === 0) {
that.$cd.html('');
that.$cd.parent().trigger('click');
clearInterval(that.timerInterval);
}
}, 1000);
} else {
console.error('Invalid option ' + opt[0] + ', must be confirm/cancel');
}
},
reactOnKey: function key(e) {
/*
* prevent keyup event if the dialog is not last!
*/
var a = $('.jconfirm');
if (a.eq(a.length - 1)[0] !== this.$el[0])
return false;
var key = e.which;
// Do not react if Enter/Space is pressed on input elements
if (this.contentDiv.find(':input').is(':focus') && /13|32/.test(key))
return false;
if ($.inArray(key, this.cancelKeys) !== -1) {
/*
* Cancel key pressed.
*/
if (this.$cancelButton) {
this.$cancelButton.click();
} else {
this.close();
}
}/*
if ($.inArray(key, this.confirmKeys) !== -1) {
*/
/*
* Confirm key pressed.
*/
/*
if (this.$confirmButton) {
this.$confirmButton.click();
}
}*/
},
setDialogCenter: function () {
if (this.$contentPane.css('display') == 'none') {
var contentHeight = 0;
var paneHeight = 0;
} else {
var contentHeight = this.$content.outerHeight();
var paneHeight = this.$contentPane.height();
if (paneHeight == 0)
paneHeight = contentHeight;
}
var off = 100;
var w = this.$content.outerWidth();
//var s = '-clip-path: inset(0px 0px '+contentHeight+'px 0px);' +
// 'clip-path: inset(0px 0px '+contentHeight+'px 0px)';
this.$content.css({
'clip': 'rect(0px ' + (off + w) + 'px ' + contentHeight + 'px -' + off + 'px)'
});
this.$contentPane.css({
'height': contentHeight
});
var windowHeight = $(window).height();
var boxHeight = this.$b.outerHeight() - paneHeight + contentHeight;
var topMargin = (windowHeight - boxHeight) / 2;
var minMargin = 100;
if (boxHeight > (windowHeight - minMargin)) {
var style = {
'margin-top': minMargin / 2,
'margin-bottom': minMargin / 2
}
$('body').addClass('jconfirm-noscroll');
} else {
var style = {
'margin-top': topMargin
}
$('body').removeClass('jconfirm-noscroll');
}
this.$b.css(style);
},
close: function () {
var that = this;
if (this.isClosed())
return false;
if (typeof this.onClose === 'function')
this.onClose();
this._unwatchContent();
that._lastFocused.focus();
//this.observer.disconnect();
/*
unbind the window resize & keyup event.
*/
$(window).unbind('resize.' + this._rand);
if (this.keyboardEnabled)
$(window).unbind('keyup.' + this._rand);
that.$el.find('.jconfirm-bg').removeClass('seen');
$('body').removeClass('jconfirm-noscroll');
this.$b.addClass(this.closeAnimation);
var closeTimer = (this.closeAnimation == 'anim-none') ? 0 : this.animationSpeed;
setTimeout(function () {
that.$el.remove();
}, closeTimer * 25 / 100);
jconfirm.record.closed += 1;
jconfirm.record.currentlyOpen -= 1;
return true;
},
open: function () {
var that = this;
if (this.isClosed())
return false;
that.$el.find('.jconfirm-bg').addClass('seen');
this.$b.removeClass(this.animation);
jconfirm.record.opened += 1;
jconfirm.record.currentlyOpen += 1;
if (typeof this.onOpen === 'function')
this.onOpen();
var jcr = 'jconfirm-box' + this._rand;
this.$b.attr('aria-labelledby', jcr).attr('tabindex', -1).focus();
this.$b.find('input[autofocus]:visible:first').focus();
if (this.$title)
this.$title.attr('id', jcr); else if (this.$content)
this.$content.attr('id', jcr);
setTimeout(function () {
that.$b.css({
'transition-property': that.$b.css('transition-property') + ', margin'
});
that._modalReady.resolve();
}, this.animationSpeed);
return true;
},
isClosed: function () {
return this.$el.css('display') === '';
}
};
jconfirm.pluginDefaults = {
template: '<div class="jconfirm"><div class="jconfirm-bg"></div><div class="jconfirm-scrollpane"><div class="container"><div class="row"><div class="jconfirm-box-container"><div class="jconfirm-box" role="dialog" aria-labelledby="labelled" tabindex="-1"><div class="closeIcon">&times;</div><div class="title-c"><span class="icon-c"></span><span class="title"></span></div><div class="content-pane"><div class="content"></div></div><div class="buttons"></div><div class="jquery-clear"></div></div></div></div></div></div></div>',
title: 'Hello',
content: 'Are you sure to continue?',
contentLoaded: function () {
},
icon: '',
opacity: 0.2,
confirmButton: 'Okay',
cancelButton: 'Close',
confirmButtonClass: 'btn-default',
cancelButtonClass: 'btn-default',
theme: 'white',
animation: 'zoom',
closeAnimation: 'scale',
animationSpeed: 500,
animationBounce: 1.2,
keyboardEnabled: false,
rtl: false,
confirmKeys: [13], // ENTER key
cancelKeys: [27], // ESC key
container: 'body',
confirm: function () {
},
cancel: function () {
},
backgroundDismiss: false,
autoClose: false,
closeIcon: null,
closeIconClass: false,
watchInterval: 100,
columnClass: 'col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3 col-xs-10 col-xs-offset-1',
onOpen: function () {
},
onClose: function () {
},
onAction: function () {
}
};
jconfirm.record = {
opened: 0,
closed: 0,
currentlyOpen: 0
};
})(jQuery);

6
kfet/static/kfet/js/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,151 @@
/*!
* JavaScript Cookie v2.1.2
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
;(function (factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
if (!converter.write) {
value = encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
} else {
value = converter.write(value, key);
}
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
return (document.cookie = [
key, '=', value,
attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE
attributes.path && '; path=' + attributes.path,
attributes.domain && '; domain=' + attributes.domain,
attributes.secure ? '; secure' : ''
].join(''));
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var cookie = parts.slice(1).join('=');
if (cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try {
var name = parts[0].replace(rdecode, decodeURIComponent);
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) {}
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
} catch (e) {}
}
return result;
}
api.set = api;
api.get = function (key) {
return api(key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init(function () {});
}));

113
kfet/static/kfet/js/kfet.js Normal file
View file

@ -0,0 +1,113 @@
$(document).ready(function() {
$(window).scroll(function() {
if ($(window).width() >= 768 && $(this).scrollTop() > 72.6) {
$('.col-content-left').css({'position':'fixed', 'top':'50px'});
$('.col-content-right').addClass('col-sm-offset-4 col-md-offset-3');
} else {
$('.col-content-left').css({'position':'relative', 'top':'0'});
$('.col-content-right').removeClass('col-sm-offset-4 col-md-offset-3');
}
});
if (typeof Cookies !== 'undefined') {
// Retrieving csrf token
csrftoken = Cookies.get('csrftoken');
// Appending csrf token to ajax post requests
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
}
});
function dateUTCToParis(date) {
return moment.tz(date, 'UTC').tz('Europe/Paris');
}
function amountDisplay(amount, is_cof=false, tri='') {
if (tri == 'LIQ')
return (- amount).toFixed(2) +'€';
return amountToUKF(amount, is_cof);
}
function amountToUKF(amount, is_cof=false) {
var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1;
return Math.round(amount * coef_cof * 10);
}
function isValidTrigramme(trigramme) {
var pattern = /^[^a-z]{3}$/;
return trigramme.match(pattern);
}
function getErrorsHtml(data) {
var content = '';
if ('operation_group' in data['errors']) {
content += 'Général';
content += '<ul>';
if (data['errors']['operation_group'].indexOf('on_acc') != -1)
content += '<li>Pas de compte sélectionné</li>';
if (data['errors']['operation_group'].indexOf('checkout') != -1)
content += '<li>Pas de caisse sélectionnée</li>';
content += '</ul>';
}
if ('missing_perms' in data['errors']) {
content += 'Permissions manquantes';
content += '<ul>';
for (var i=0; i<data['errors']['missing_perms'].length; i++)
content += '<li>'+data['errors']['missing_perms'][i]+'</li>';
content += '</ul>';
}
if ('negative' in data['errors']) {
var url_base = "{% url 'kfet.account.update' LIQ}";
url_base = base_url(0, url_base.length-8);
for (var i=0; i<data['errors']['negative'].length; i++) {
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank" style="width:100%">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
}
}
if ('addcost' in data['errors']) {
content += '<ul>';
if (data['errors']['addcost'].indexOf('__all__') != -1)
content += '<li>Compte invalide</li>';
if (data['errors']['addcost'].indexOf('amount') != -1)
content += '<li>Montant invalide</li>';
content += '</ul>';
}
return content;
}
function requestAuth(data, callback, focus_next = null) {
var content = getErrorsHtml(data);
content += '<input type="password" name="password" autofocus>',
$.confirm({
title: 'Authentification requise',
content: content,
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirm: function() {
var password = this.$content.find('input').val();
callback(password);
},
onOpen: function() {
var that = this;
this.$content.find('input').on('keypress', function(e) {
if (e.keyCode == 13)
that.$confirmButton.click();
});
},
onClose: function() {
if (focus_next)
this._lastFocused = focus_next;
}
});
}

View file

@ -0,0 +1,64 @@
//! moment.js locale configuration
//! locale : French [fr]
//! author : John Fischer : https://github.com/jfroffice
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
var fr = moment.defineLocale('fr', {
months : 'Janvier_Février_Mars_Avril_Mai_Juin_Juillet_Août_Septembre_Octobre_Novembre_Décembre'.split('_'),
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
monthsParseExact : true,
weekdays : 'Dimanche_Lundi_Mardi_Mercredi_Jeudi_Vendredi_Samedi'.split('_'),
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Aujourd\'hui à] LT',
nextDay: '[Demain à] LT',
nextWeek: 'dddd [à] LT',
lastDay: '[Hier à] LT',
lastWeek: 'dddd [dernier à] LT',
sameElse: 'L'
},
relativeTime : {
future : 'dans %s',
past : 'il y a %s',
s : 'quelques secondes',
m : 'une minute',
mm : '%d minutes',
h : 'une heure',
hh : '%d heures',
d : 'un jour',
dd : '%d jours',
M : 'un mois',
MM : '%d mois',
y : 'un an',
yy : '%d ans'
},
ordinalParse: /\d{1,2}(er|)/,
ordinal : function (number) {
return number + (number === 1 ? 'er' : '');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return fr;
}));

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,782 @@
/**
* @author zhixin wen <wenzhixin2010@gmail.com>
* @version 1.2.1
*
* http://wenzhixin.net.cn/p/multiple-select/
*/
(function ($) {
'use strict';
// it only does '%s', and return '' when arguments are undefined
var sprintf = function (str) {
var args = arguments,
flag = true,
i = 1;
str = str.replace(/%s/g, function () {
var arg = args[i++];
if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
};
var removeDiacritics = function (str) {
var defaultDiacriticsRemovalMap = [
{'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
{'base':'AA','letters':/[\uA732]/g},
{'base':'AE','letters':/[\u00C6\u01FC\u01E2]/g},
{'base':'AO','letters':/[\uA734]/g},
{'base':'AU','letters':/[\uA736]/g},
{'base':'AV','letters':/[\uA738\uA73A]/g},
{'base':'AY','letters':/[\uA73C]/g},
{'base':'B', 'letters':/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
{'base':'C', 'letters':/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
{'base':'D', 'letters':/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
{'base':'DZ','letters':/[\u01F1\u01C4]/g},
{'base':'Dz','letters':/[\u01F2\u01C5]/g},
{'base':'E', 'letters':/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
{'base':'F', 'letters':/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
{'base':'G', 'letters':/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
{'base':'H', 'letters':/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
{'base':'I', 'letters':/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
{'base':'J', 'letters':/[\u004A\u24BF\uFF2A\u0134\u0248]/g},
{'base':'K', 'letters':/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
{'base':'L', 'letters':/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
{'base':'LJ','letters':/[\u01C7]/g},
{'base':'Lj','letters':/[\u01C8]/g},
{'base':'M', 'letters':/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
{'base':'N', 'letters':/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
{'base':'NJ','letters':/[\u01CA]/g},
{'base':'Nj','letters':/[\u01CB]/g},
{'base':'O', 'letters':/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
{'base':'OI','letters':/[\u01A2]/g},
{'base':'OO','letters':/[\uA74E]/g},
{'base':'OU','letters':/[\u0222]/g},
{'base':'P', 'letters':/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
{'base':'Q', 'letters':/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
{'base':'R', 'letters':/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
{'base':'S', 'letters':/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
{'base':'T', 'letters':/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
{'base':'TZ','letters':/[\uA728]/g},
{'base':'U', 'letters':/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
{'base':'V', 'letters':/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
{'base':'VY','letters':/[\uA760]/g},
{'base':'W', 'letters':/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
{'base':'X', 'letters':/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
{'base':'Y', 'letters':/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
{'base':'Z', 'letters':/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
{'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
{'base':'aa','letters':/[\uA733]/g},
{'base':'ae','letters':/[\u00E6\u01FD\u01E3]/g},
{'base':'ao','letters':/[\uA735]/g},
{'base':'au','letters':/[\uA737]/g},
{'base':'av','letters':/[\uA739\uA73B]/g},
{'base':'ay','letters':/[\uA73D]/g},
{'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
{'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
{'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
{'base':'dz','letters':/[\u01F3\u01C6]/g},
{'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
{'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
{'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
{'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
{'base':'hv','letters':/[\u0195]/g},
{'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
{'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
{'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
{'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
{'base':'lj','letters':/[\u01C9]/g},
{'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
{'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
{'base':'nj','letters':/[\u01CC]/g},
{'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
{'base':'oi','letters':/[\u01A3]/g},
{'base':'ou','letters':/[\u0223]/g},
{'base':'oo','letters':/[\uA74F]/g},
{'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
{'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
{'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
{'base':'s','letters':/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
{'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
{'base':'tz','letters':/[\uA729]/g},
{'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
{'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
{'base':'vy','letters':/[\uA761]/g},
{'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
{'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
{'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
{'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
];
for (var i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
str = str.replace(defaultDiacriticsRemovalMap[i].letters, defaultDiacriticsRemovalMap[i].base);
}
return str;
};
function MultipleSelect($el, options) {
var that = this,
name = $el.attr('name') || options.name || '';
this.options = options;
// hide select element
this.$el = $el.hide();
// label element
this.$label = this.$el.closest('label');
if (this.$label.length === 0 && this.$el.attr('id')) {
this.$label = $(sprintf('label[for="%s"]', this.$el.attr('id').replace(/:/g, '\\:')));
}
// restore class and title from select element
this.$parent = $(sprintf(
'<div class="ms-parent %s" %s/>',
$el.attr('class') || '',
sprintf('title="%s"', $el.attr('title'))));
// add placeholder to choice button
this.$choice = $(sprintf([
'<button type="button" class="ms-choice">',
'<span class="placeholder">%s</span>',
'<div></div>',
'</button>'
].join(''),
this.options.placeholder));
// default position is bottom
this.$drop = $(sprintf('<div class="ms-drop %s"%s></div>',
this.options.position,
sprintf(' style="width: %s"', this.options.dropWidth)));
this.$el.after(this.$parent);
this.$parent.append(this.$choice);
this.$parent.append(this.$drop);
if (this.$el.prop('disabled')) {
this.$choice.addClass('disabled');
}
this.$parent.css('width',
this.options.width ||
this.$el.css('width') ||
this.$el.outerWidth() + 20);
this.selectAllName = 'data-name="selectAll' + name + '"';
this.selectGroupName = 'data-name="selectGroup' + name + '"';
this.selectItemName = 'data-name="selectItem' + name + '"';
if (!this.options.keepOpen) {
$(document).click(function (e) {
if ($(e.target)[0] === that.$choice[0] ||
$(e.target).parents('.ms-choice')[0] === that.$choice[0]) {
return;
}
if (($(e.target)[0] === that.$drop[0] ||
$(e.target).parents('.ms-drop')[0] !== that.$drop[0] && e.target !== $el[0]) &&
that.options.isOpen) {
that.close();
}
});
}
}
MultipleSelect.prototype = {
constructor: MultipleSelect,
init: function () {
var that = this,
$ul = $('<ul></ul>');
this.$drop.html('');
if (this.options.filter) {
this.$drop.append([
'<div class="ms-search">',
'<input type="text" autocomplete="off" autocorrect="off" autocapitilize="off" spellcheck="false">',
'</div>'].join('')
);
}
if (this.options.selectAll && !this.options.single) {
$ul.append([
'<li class="ms-select-all">',
'<label>',
sprintf('<input type="checkbox" %s /> ', this.selectAllName),
this.options.selectAllDelimiter[0],
this.options.selectAllText,
this.options.selectAllDelimiter[1],
'</label>',
'</li>'
].join(''));
}
$.each(this.$el.children(), function (i, elm) {
$ul.append(that.optionToHtml(i, elm));
});
$ul.append(sprintf('<li class="ms-no-results">%s</li>', this.options.noMatchesFound));
this.$drop.append($ul);
this.$drop.find('ul').css('max-height', this.options.maxHeight + 'px');
this.$drop.find('.multiple').css('width', this.options.multipleWidth + 'px');
this.$searchInput = this.$drop.find('.ms-search input');
this.$selectAll = this.$drop.find('input[' + this.selectAllName + ']');
this.$selectGroups = this.$drop.find('input[' + this.selectGroupName + ']');
this.$selectItems = this.$drop.find('input[' + this.selectItemName + ']:enabled');
this.$disableItems = this.$drop.find('input[' + this.selectItemName + ']:disabled');
this.$noResults = this.$drop.find('.ms-no-results');
this.events();
this.updateSelectAll(true);
this.update(true);
if (this.options.isOpen) {
this.open();
}
},
optionToHtml: function (i, elm, group, groupDisabled) {
var that = this,
$elm = $(elm),
classes = $elm.attr('class') || '',
title = sprintf('title="%s"', $elm.attr('title')),
multiple = this.options.multiple ? 'multiple' : '',
disabled,
type = this.options.single ? 'radio' : 'checkbox';
if ($elm.is('option')) {
var value = $elm.val(),
text = that.options.textTemplate($elm),
selected = $elm.prop('selected'),
style = sprintf('style="%s"', this.options.styler(value)),
$el;
disabled = groupDisabled || $elm.prop('disabled');
$el = $([
sprintf('<li class="%s %s" %s %s>', multiple, classes, title, style),
sprintf('<label class="%s">', disabled ? 'disabled' : ''),
sprintf('<input type="%s" %s%s%s%s>',
type, this.selectItemName,
selected ? ' checked="checked"' : '',
disabled ? ' disabled="disabled"' : '',
sprintf(' data-group="%s"', group)),
sprintf('<span>%s</span>', text),
'</label>',
'</li>'
].join(''));
$el.find('input').val(value);
return $el;
}
if ($elm.is('optgroup')) {
var label = that.options.labelTemplate($elm),
$group = $('<div/>');
group = 'group_' + i;
disabled = $elm.prop('disabled');
$group.append([
'<li class="group">',
sprintf('<label class="optgroup %s" data-group="%s">', disabled ? 'disabled' : '', group),
this.options.hideOptgroupCheckboxes || this.options.single ? '' :
sprintf('<input type="checkbox" %s %s>',
this.selectGroupName, disabled ? 'disabled="disabled"' : ''),
label,
'</label>',
'</li>'
].join(''));
$.each($elm.children(), function (i, elm) {
$group.append(that.optionToHtml(i, elm, group, disabled));
});
return $group.html();
}
},
events: function () {
var that = this,
toggleOpen = function (e) {
e.preventDefault();
that[that.options.isOpen ? 'close' : 'open']();
};
if (this.$label) {
this.$label.off('click').on('click', function (e) {
if (e.target.nodeName.toLowerCase() !== 'label' || e.target !== this) {
return;
}
toggleOpen(e);
if (!that.options.filter || !that.options.isOpen) {
that.focus();
}
e.stopPropagation(); // Causes lost focus otherwise
});
}
this.$choice.off('click').on('click', toggleOpen)
.off('focus').on('focus', this.options.onFocus)
.off('blur').on('blur', this.options.onBlur);
this.$parent.off('keydown').on('keydown', function (e) {
switch (e.which) {
case 27: // esc key
that.close();
that.$choice.focus();
break;
}
});
this.$searchInput.off('keydown').on('keydown',function (e) {
// Ensure shift-tab causes lost focus from filter as with clicking away
if (e.keyCode === 9 && e.shiftKey) {
that.close();
}
}).off('keyup').on('keyup', function (e) {
// enter or space
// Avoid selecting/deselecting if no choices made
if (that.options.filterAcceptOnEnter && (e.which === 13 || e.which == 32) && that.$searchInput.val()) {
that.$selectAll.click();
that.close();
that.focus();
return;
}
that.filter();
});
this.$selectAll.off('click').on('click', function () {
var checked = $(this).prop('checked'),
$items = that.$selectItems.filter(':visible');
if ($items.length === that.$selectItems.length) {
that[checked ? 'checkAll' : 'uncheckAll']();
} else { // when the filter option is true
that.$selectGroups.prop('checked', checked);
$items.prop('checked', checked);
that.options[checked ? 'onCheckAll' : 'onUncheckAll']();
that.update();
}
});
this.$selectGroups.off('click').on('click', function () {
var group = $(this).parent().attr('data-group'),
$items = that.$selectItems.filter(':visible'),
$children = $items.filter(sprintf('[data-group="%s"]', group)),
checked = $children.length !== $children.filter(':checked').length;
$children.prop('checked', checked);
that.updateSelectAll();
that.update();
that.options.onOptgroupClick({
label: $(this).parent().text(),
checked: checked,
children: $children.get(),
instance: that
});
});
this.$selectItems.off('click').on('click', function () {
that.updateSelectAll();
that.update();
that.updateOptGroupSelect();
that.options.onClick({
label: $(this).parent().text(),
value: $(this).val(),
checked: $(this).prop('checked'),
instance: that
});
if (that.options.single && that.options.isOpen && !that.options.keepOpen) {
that.close();
}
if (that.options.single) {
var clickedVal = $(this).val();
that.$selectItems.filter(function() {
return $(this).val() !== clickedVal;
}).each(function() {
$(this).prop('checked', false);
});
that.update();
}
});
},
open: function () {
if (this.$choice.hasClass('disabled')) {
return;
}
this.options.isOpen = true;
this.$choice.find('>div').addClass('open');
this.$drop[this.animateMethod('show')]();
// fix filter bug: no results show
this.$selectAll.parent().show();
this.$noResults.hide();
// Fix #77: 'All selected' when no options
if (!this.$el.children().length) {
this.$selectAll.parent().hide();
this.$noResults.show();
}
if (this.options.container) {
var offset = this.$drop.offset();
this.$drop.appendTo($(this.options.container));
this.$drop.offset({
top: offset.top,
left: offset.left
});
}
if (this.options.filter) {
this.$searchInput.val('');
this.$searchInput.focus();
this.filter();
}
this.options.onOpen();
},
close: function () {
this.options.isOpen = false;
this.$choice.find('>div').removeClass('open');
this.$drop[this.animateMethod('hide')]();
if (this.options.container) {
this.$parent.append(this.$drop);
this.$drop.css({
'top': 'auto',
'left': 'auto'
});
}
this.options.onClose();
},
animateMethod: function (method) {
var methods = {
show: {
fade: 'fadeIn',
slide: 'slideDown'
},
hide: {
fade: 'fadeOut',
slide: 'slideUp'
}
};
return methods[method][this.options.animate] || method;
},
update: function (isInit) {
var selects = this.options.displayValues ? this.getSelects() : this.getSelects('text'),
$span = this.$choice.find('>span'),
sl = selects.length;
if (sl === 0) {
$span.addClass('placeholder').html(this.options.placeholder);
} else if (this.options.allSelected && sl === this.$selectItems.length + this.$disableItems.length) {
$span.removeClass('placeholder').html(this.options.allSelected);
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
$span.removeClass('placeholder').text(selects.slice(0, this.options.minimumCountSelected)
.join(this.options.delimiter) + '...');
} else if (this.options.countSelected && sl > this.options.minimumCountSelected) {
$span.removeClass('placeholder').html(this.options.countSelected
.replace('#', selects.length)
.replace('%', this.$selectItems.length + this.$disableItems.length));
} else {
$span.removeClass('placeholder').text(selects.join(this.options.delimiter));
}
if (this.options.addTitle) {
$span.prop('title', this.getSelects('text'));
}
// set selects to select
this.$el.val(this.getSelects()).trigger('change');
// add selected class to selected li
this.$drop.find('li').removeClass('selected');
this.$drop.find('input:checked').each(function () {
$(this).parents('li').first().addClass('selected');
});
// trigger <select> change event
if (!isInit) {
this.$el.trigger('change');
}
},
updateSelectAll: function (isInit) {
var $items = this.$selectItems;
if (!isInit) {
$items = $items.filter(':visible');
}
this.$selectAll.prop('checked', $items.length &&
$items.length === $items.filter(':checked').length);
if (!isInit && this.$selectAll.prop('checked')) {
this.options.onCheckAll();
}
},
updateOptGroupSelect: function () {
var $items = this.$selectItems.filter(':visible');
$.each(this.$selectGroups, function (i, val) {
var group = $(val).parent().attr('data-group'),
$children = $items.filter(sprintf('[data-group="%s"]', group));
$(val).prop('checked', $children.length &&
$children.length === $children.filter(':checked').length);
});
},
//value or text, default: 'value'
getSelects: function (type) {
var that = this,
texts = [],
values = [];
this.$drop.find(sprintf('input[%s]:checked', this.selectItemName)).each(function () {
texts.push($(this).parents('li').first().text());
values.push($(this).val());
});
if (type === 'text' && this.$selectGroups.length) {
texts = [];
this.$selectGroups.each(function () {
var html = [],
text = $.trim($(this).parent().text()),
group = $(this).parent().data('group'),
$children = that.$drop.find(sprintf('[%s][data-group="%s"]', that.selectItemName, group)),
$selected = $children.filter(':checked');
if (!$selected.length) {
return;
}
html.push('[');
html.push(text);
if ($children.length > $selected.length) {
var list = [];
$selected.each(function () {
list.push($(this).parent().text());
});
html.push(': ' + list.join(', '));
}
html.push(']');
texts.push(html.join(''));
});
}
return type === 'text' ? texts : values;
},
setSelects: function (values) {
var that = this;
this.$selectItems.prop('checked', false);
this.$disableItems.prop('checked', false);
$.each(values, function (i, value) {
that.$selectItems.filter(sprintf('[value="%s"]', value)).prop('checked', true);
that.$disableItems.filter(sprintf('[value="%s"]', value)).prop('checked', true);
});
this.$selectAll.prop('checked', this.$selectItems.length ===
this.$selectItems.filter(':checked').length + this.$disableItems.filter(':checked').length);
$.each(that.$selectGroups, function (i, val) {
var group = $(val).parent().attr('data-group'),
$children = that.$selectItems.filter('[data-group="' + group + '"]');
$(val).prop('checked', $children.length &&
$children.length === $children.filter(':checked').length);
});
this.update();
},
enable: function () {
this.$choice.removeClass('disabled');
},
disable: function () {
this.$choice.addClass('disabled');
},
checkAll: function () {
this.$selectItems.prop('checked', true);
this.$selectGroups.prop('checked', true);
this.$selectAll.prop('checked', true);
this.update();
this.options.onCheckAll();
},
uncheckAll: function () {
this.$selectItems.prop('checked', false);
this.$selectGroups.prop('checked', false);
this.$selectAll.prop('checked', false);
this.update();
this.options.onUncheckAll();
},
focus: function () {
this.$choice.focus();
this.options.onFocus();
},
blur: function () {
this.$choice.blur();
this.options.onBlur();
},
refresh: function () {
this.init();
},
filter: function () {
var that = this,
text = $.trim(this.$searchInput.val()).toLowerCase();
if (text.length === 0) {
this.$selectAll.parent().show();
this.$selectItems.parent().show();
this.$disableItems.parent().show();
this.$selectGroups.parent().show();
this.$noResults.hide();
} else {
this.$selectItems.each(function () {
var $parent = $(this).parent();
$parent[removeDiacritics($parent.text().toLowerCase()).indexOf(removeDiacritics(text)) < 0 ? 'hide' : 'show']();
});
this.$disableItems.parent().hide();
this.$selectGroups.each(function () {
var $parent = $(this).parent();
var group = $parent.attr('data-group'),
$items = that.$selectItems.filter(':visible');
$parent[$items.filter(sprintf('[data-group="%s"]', group)).length ? 'show' : 'hide']();
});
//Check if no matches found
if (this.$selectItems.parent().filter(':visible').length) {
this.$selectAll.parent().show();
this.$noResults.hide();
} else {
this.$selectAll.parent().hide();
this.$noResults.show();
}
}
this.updateOptGroupSelect();
this.updateSelectAll();
this.options.onFilter(text);
}
};
$.fn.multipleSelect = function () {
var option = arguments[0],
args = arguments,
value,
allowedMethods = [
'getSelects', 'setSelects',
'enable', 'disable',
'open', 'close',
'checkAll', 'uncheckAll',
'focus', 'blur',
'refresh', 'close'
];
this.each(function () {
var $this = $(this),
data = $this.data('multipleSelect'),
options = $.extend({}, $.fn.multipleSelect.defaults,
$this.data(), typeof option === 'object' && option);
if (!data) {
data = new MultipleSelect($this, options);
$this.data('multipleSelect', data);
}
if (typeof option === 'string') {
if ($.inArray(option, allowedMethods) < 0) {
throw 'Unknown method: ' + option;
}
value = data[option](args[1]);
} else {
data.init();
if (args[1]) {
value = data[args[1]].apply(data, [].slice.call(args, 2));
}
}
});
return typeof value !== 'undefined' ? value : this;
};
$.fn.multipleSelect.defaults = {
name: '',
isOpen: false,
placeholder: '',
selectAll: true,
selectAllDelimiter: ['[', ']'],
minimumCountSelected: 3,
ellipsis: false,
multiple: false,
multipleWidth: 80,
single: false,
filter: false,
width: undefined,
dropWidth: undefined,
maxHeight: 250,
container: null,
position: 'bottom',
keepOpen: false,
animate: 'none', // 'none', 'fade', 'slide'
displayValues: false,
delimiter: ', ',
addTitle: false,
filterAcceptOnEnter: false,
hideOptgroupCheckboxes: false,
selectAllText: 'Select all',
allSelected: 'All selected',
countSelected: '# of % selected',
noMatchesFound: 'No matches found',
styler: function () {
return false;
},
textTemplate: function ($elm) {
return $elm.html();
},
labelTemplate: function ($elm) {
return $elm.attr('label');
},
onOpen: function () {
return false;
},
onClose: function () {
return false;
},
onCheckAll: function () {
return false;
},
onUncheckAll: function () {
return false;
},
onFocus: function () {
return false;
},
onBlur: function () {
return false;
},
onOptgroupClick: function () {
return false;
},
onClick: function () {
return false;
},
onFilter: function () {
return false;
}
};
})(jQuery);

View file

@ -0,0 +1,365 @@
// MIT License:
//
// Copyright (c) 2010-2012, Joe Walnes
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* This behaves like a WebSocket in every way, except if it fails to connect,
* or it gets disconnected, it will repeatedly poll until it successfully connects
* again.
*
* It is API compatible, so when you have:
* ws = new WebSocket('ws://....');
* you can replace with:
* ws = new ReconnectingWebSocket('ws://....');
*
* The event stream will typically look like:
* onconnecting
* onopen
* onmessage
* onmessage
* onclose // lost connection
* onconnecting
* onopen // sometime later...
* onmessage
* onmessage
* etc...
*
* It is API compatible with the standard WebSocket API, apart from the following members:
*
* - `bufferedAmount`
* - `extensions`
* - `binaryType`
*
* Latest version: https://github.com/joewalnes/reconnecting-websocket/
* - Joe Walnes
*
* Syntax
* ======
* var socket = new ReconnectingWebSocket(url, protocols, options);
*
* Parameters
* ==========
* url - The url you are connecting to.
* protocols - Optional string or array of protocols.
* options - See below
*
* Options
* =======
* Options can either be passed upon instantiation or set after instantiation:
*
* var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
*
* or
*
* var socket = new ReconnectingWebSocket(url);
* socket.debug = true;
* socket.reconnectInterval = 4000;
*
* debug
* - Whether this instance should log debug messages. Accepts true or false. Default: false.
*
* automaticOpen
* - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
*
* reconnectInterval
* - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
*
* maxReconnectInterval
* - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
*
* reconnectDecay
* - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
*
* timeoutInterval
* - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
*
*/
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module !== 'undefined' && module.exports){
module.exports = factory();
} else {
global.ReconnectingWebSocket = factory();
}
})(this, function () {
if (!('WebSocket' in window)) {
return;
}
function ReconnectingWebSocket(url, protocols, options) {
// Default settings
var settings = {
/** Whether this instance should log debug messages. */
debug: false,
/** Whether or not the websocket should attempt to connect immediately upon instantiation. */
automaticOpen: true,
/** The number of milliseconds to delay before attempting to reconnect. */
reconnectInterval: 1000,
/** The maximum number of milliseconds to delay a reconnection attempt. */
maxReconnectInterval: 30000,
/** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
reconnectDecay: 1.5,
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
timeoutInterval: 2000,
/** The maximum number of reconnection attempts to make. Unlimited if null. */
maxReconnectAttempts: null,
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
binaryType: 'blob'
}
if (!options) { options = {}; }
// Overwrite and define settings with options if they exist.
for (var key in settings) {
if (typeof options[key] !== 'undefined') {
this[key] = options[key];
} else {
this[key] = settings[key];
}
}
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
this.url = url;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
this.reconnectAttempts = 0;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
this.readyState = WebSocket.CONNECTING;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
this.protocol = null;
// Private state variables
var self = this;
var ws;
var forcedClose = false;
var timedOut = false;
var eventTarget = document.createElement('div');
// Wire up "on*" properties as event handlers
eventTarget.addEventListener('open', function(event) { self.onopen(event); });
eventTarget.addEventListener('close', function(event) { self.onclose(event); });
eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
eventTarget.addEventListener('error', function(event) { self.onerror(event); });
// Expose the API required by EventTarget
this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
/**
* This function generates an event that is compatible with standard
* compliant browsers and IE9 - IE11
*
* This will prevent the error:
* Object doesn't support this action
*
* http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
* @param s String The name that the event should use
* @param args Object an optional object that the event will use
*/
function generateEvent(s, args) {
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(s, false, false, args);
return evt;
};
this.open = function (reconnectAttempt) {
ws = new WebSocket(self.url, protocols || []);
ws.binaryType = this.binaryType;
if (reconnectAttempt) {
if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
return;
}
} else {
eventTarget.dispatchEvent(generateEvent('connecting'));
this.reconnectAttempts = 0;
}
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
}
var localWs = ws;
var timeout = setTimeout(function() {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
}
timedOut = true;
localWs.close();
timedOut = false;
}, self.timeoutInterval);
ws.onopen = function(event) {
clearTimeout(timeout);
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onopen', self.url);
}
self.protocol = ws.protocol;
self.readyState = WebSocket.OPEN;
self.reconnectAttempts = 0;
var e = generateEvent('open');
e.isReconnect = reconnectAttempt;
reconnectAttempt = false;
eventTarget.dispatchEvent(e);
};
ws.onclose = function(event) {
clearTimeout(timeout);
ws = null;
if (forcedClose) {
self.readyState = WebSocket.CLOSED;
eventTarget.dispatchEvent(generateEvent('close'));
} else {
self.readyState = WebSocket.CONNECTING;
var e = generateEvent('connecting');
e.code = event.code;
e.reason = event.reason;
e.wasClean = event.wasClean;
eventTarget.dispatchEvent(e);
if (!reconnectAttempt && !timedOut) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onclose', self.url);
}
eventTarget.dispatchEvent(generateEvent('close'));
}
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
setTimeout(function() {
self.reconnectAttempts++;
self.open(true);
}, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
}
};
ws.onmessage = function(event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
}
var e = generateEvent('message');
e.data = event.data;
eventTarget.dispatchEvent(e);
};
ws.onerror = function(event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
}
eventTarget.dispatchEvent(generateEvent('error'));
};
}
// Whether or not to create a websocket upon instantiation
if (this.automaticOpen == true) {
this.open(false);
}
/**
* Transmits data to the server over the WebSocket connection.
*
* @param data a text string, ArrayBuffer or Blob to send to the server.
*/
this.send = function(data) {
if (ws) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'send', self.url, data);
}
return ws.send(data);
} else {
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
}
};
/**
* Closes the WebSocket connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
this.close = function(code, reason) {
// Default CLOSE_NORMAL code
if (typeof code == 'undefined') {
code = 1000;
}
forcedClose = true;
if (ws) {
ws.close(code, reason);
}
};
/**
* Additional public API method to refresh the connection if still open (close, re-open).
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
this.refresh = function() {
if (ws) {
ws.close();
}
};
}
/**
* An event listener to be called when the WebSocket connection's readyState changes to OPEN;
* this indicates that the connection is ready to send and receive data.
*/
ReconnectingWebSocket.prototype.onopen = function(event) {};
/** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
ReconnectingWebSocket.prototype.onclose = function(event) {};
/** An event listener to be called when a connection begins being attempted. */
ReconnectingWebSocket.prototype.onconnecting = function(event) {};
/** An event listener to be called when a message is received from the server. */
ReconnectingWebSocket.prototype.onmessage = function(event) {};
/** An event listener to be called when an error occurs. */
ReconnectingWebSocket.prototype.onerror = function(event) {};
/**
* Whether all instances of ReconnectingWebSocket should log debug messages.
* Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
*/
ReconnectingWebSocket.debugAll = false;
ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
ReconnectingWebSocket.OPEN = WebSocket.OPEN;
ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
return ReconnectingWebSocket;
});

View file

@ -0,0 +1,68 @@
{% extends "kfet/base.html" %}
{% block title %}Liste des comptes{% endblock %}
{% block content-header-title %}Comptes{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
<div class="content-left-top">
<div class="line line-big">{{ accounts|length|add:-1 }}</div>
<div class="line line-bigsub">compte{{ accounts|length|add:-1|pluralize }}</div>
</div>
<div class="buttons">
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.create' %}">Créer un compte</a>
{% if perms.kfet.manage_perms %}
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group' %}">Permissions</a>
{% endif %}
{% if perms.kfet.view_negs %}
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.negative' %}">Négatifs</a>
{% endif %}
</div>
</div>
</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">
<h2>Liste des comptes</h2>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<td></td>
<td>Trigramme</td>
<td>Nom</td>
<td>Balance</td>
<td>COF</td>
<td>Dpt</td>
<td>Promo</td>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr>
<td class="text-center">
<a href="{% url 'kfet.account.read' account.trigramme %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</td>
<td>{{ account.trigramme }}</td>
<td>{{ account.name }}</td>
<td class="text-right">{{ account.balance }}€</td>
<td>{{ account.is_cof }}</td>
<td>{{ account.departement }}</td>
<td>{{ account.promo|default_if_none:'' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,95 @@
{% extends "kfet/base.html" %}
{% load staticfiles %}
{% block title %}Nouveau compte{% endblock %}
{% block extra_head %}
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
{% endblock %}
{% block content-header-title %}Création d'un compte{% endblock %}
{% block content %}
{% 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 action="{% url "kfet.account.create" %}" method="post" class="account_create">
{% csrf_token %}
<div>
{{ trigramme_form.trigramme.errors }}
{{ trigramme_form.trigramme }}
</div>
<div id="trigramme_valid"></div>
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
<div style="position:relative;">
<div id="search_results"></div>
</div>
<div class="form-horizontal">
<div id="form-placeholder">
{% include 'kfet/account_create_form.html' %}
</div>
{% if not perms.kfet.add_account %}
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
</div>
</form>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
// Affichage des résultats d'autocomplétion
$('input#search_autocomplete').yourlabsAutocomplete({
url: '{% url "kfet.account.create.autocomplete" %}',
minimumCharacters: 0,
id: 'search_autocomplete',
choiceSelector: 'li:has(a)',
container: $("#search_results"),
box: $("#search_results"),
});
// Chargement du formulaire adapté au choix sélectionné
$('input#search_autocomplete').bind(
'selectChoice',
function(e, choice, autocomplete) {
autocomplete.hide();
link = choice.find('a:first');
if (link.length && link.attr('href') != undefined) {
$('#form-placeholder').html("").load(link.attr('href'));
}
}
);
// Vérification client de la validité
// et de ladisponibilité du trigramme choisi
$('#id_trigramme').on('input', function() {
var trigramme = $('#id_trigramme').val().toUpperCase();
var container = '#trigramme_valid';
var pattern = /^[^a-z]{3}$/;
if (!(trigramme.match(pattern))) {
$('#id_trigramme')
.css('background', '#fff')
.css('color', '#000');
} else {
$.ajax({
dataType: "json",
url: "{% url "kfet.account.is_validandfree.ajax" %}",
data: { trigramme: trigramme },
}).done(function(data) {
if (data['is_free']) {
$('#id_trigramme').css('background', '#009011');
} else {
$('#id_trigramme').css('background', '#C8102E');
}
$('#id_trigramme').css('color', '#fff');
});
}
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,49 @@
{% load kfet_tags %}
<ul>
<li>
<a href="{% url "kfet.account.create.empty" %}">
Créer un compte
</a>
</li>
{% if kfet %}
<li class="user_category"><span class="text">Comptes existant</span></li>
{% for account, user in kfet %}
<li><span class="text">{{ account }} [{{ user|highlight_user:q }}]</span></li>
{% endfor %}
{% endif %}
{% if users_cof %}
<li class="user_category"><span class="text">Membres du COF</span></li>
{% for user in users_cof %}
<li>
<a href="{% url "kfet.account.create.fromuser" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% endfor %}
{% endif %}
{% if users_notcof %}
<li class="user_category"><span class="text">Non-membres du COF</span></li>
{% for user in users_notcof %}
<li>
<a href="{% url "kfet.account.create.fromuser" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% endfor %}
{% endif %}
{% if clippers %}
<li class="user_category"><span class="text">Utilisateurs clipper</span></li>
{% for clipper in clippers %}
<li>
<a href="{% url "kfet.account.create.fromclipper" clipper.username %}">
{{ clipper|highlight_clipper:q }}
</li>
{% endfor %}
{% endif %}
{% if not q %}
<li class="user_category"><span class="text">Pas de recherche, pas de résultats !</span></li>
{% elif not options %}
<li class="user_category"><span class="text">Aucune correspondance trouvée :-(</span></li>
{% endif %}
</ul>

View file

@ -0,0 +1,9 @@
{% load widget_tweaks %}
{% include 'kfet/form_snippet.html' with form=user_form %}
{% include 'kfet/form_snippet.html' with form=cof_form %}
{% include 'kfet/form_snippet.html' with form=account_form %}
{% if user_form %}
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
{% endif %}

View file

@ -0,0 +1,96 @@
{% extends "kfet/base.html" %}
{% load staticfiles %}
{% block title %}Nouveau compte{% endblock %}
{% block extra_head %}
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
{% endblock %}
{% block content-header-title %}Création d'un compte{% endblock %}
{% block content %}
{% 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 action="{% url "kfet.account.create_special" %}" method="post" class="account_create">
{% csrf_token %}
<div>
{{ trigramme_form.trigramme.errors }}
{{ trigramme_form.trigramme }}
{{ balance_form }}
</div>
<div id="trigramme_valid"></div>
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
<div style="position:relative;">
<div id="search_results"></div>
</div>
<div class="form-horizontal">
<div id="form-placeholder">
{% include 'kfet/account_create_form.html' %}
</div>
{% if not perms.kfet.add_account %}
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
</div>
</form>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
// Affichage des résultats d'autocomplétion
$('input#search_autocomplete').yourlabsAutocomplete({
url: '{% url "kfet.account.create.autocomplete" %}',
minimumCharacters: 0,
id: 'search_autocomplete',
choiceSelector: 'li:has(a)',
container: $("#search_results"),
box: $("#search_results"),
});
// Chargement du formulaire adapté au choix sélectionné
$('input#search_autocomplete').bind(
'selectChoice',
function(e, choice, autocomplete) {
autocomplete.hide();
link = choice.find('a:first');
if (link.length && link.attr('href') != undefined) {
$('#form-placeholder').html("").load(link.attr('href'));
}
}
);
// Vérification client de la validité
// et de ladisponibilité du trigramme choisi
$('#id_trigramme').on('input', function() {
var trigramme = $('#id_trigramme').val().toUpperCase();
var container = '#trigramme_valid';
var pattern = /^[^a-z]{3}$/;
if (!(trigramme.match(pattern))) {
$('#id_trigramme')
.css('background', '#fff')
.css('color', '#000');
} else {
$.ajax({
dataType: "json",
url: "{% url "kfet.account.is_validandfree.ajax" %}",
data: { trigramme: trigramme },
}).done(function(data) {
if (data['is_free']) {
$('#id_trigramme').css('background', '#009011');
} else {
$('#id_trigramme').css('background', '#C8102E');
}
$('#id_trigramme').css('color', '#fff');
});
}
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,54 @@
{% extends 'kfet/base.html' %}
{% block title %}Groupes de comptes{% endblock %}
{% block content-header-title %}Groupes de comptes{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
<div class="content-left-top text-center">
<div class="line"></div>
</div>
<div class="buttons">
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group.create' %}">Créer un groupe</a>
</div>
</div>
</div>
<div class="col-sm-8 col-md-9 col-content-right">
{% include 'kfet/base_messages.html' %}
<div class="content-right">
{% for group in groups %}
<div class="content-right-block">
<div class="buttons-title">
<a class="btn btn-primary" href="{% url 'kfet.account.group.update' group.pk %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</div>
<h2>{{ group.name }}</h2>
<div class="row">
<div class="col-sm-6">
<h3>Permissions</h3>
<ul>
{% for perm in group.permissions.all %}
<li>{{ perm.name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-sm-6">
<h3>Comptes</h3>
<ul>
{% for user in group.user_set.all %}
<li>{{ user.profile.account_kfet }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends 'kfet/base.html' %}
{% load staticfiles %}
{% block extra_head %}
<link rel="stylesheet" text="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
<script src="{% static 'kfet/js/multiple-select.js' %}"></script>
{% endblock %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<div>
{{ form.name.errors }}
{{ form.name.label_tag }}
<div class="input-group">
<span class="input-group-addon">K-Fêt</span>
{{ form.name }}
</div>
</div>
<div>
{{ form.permissions.errors }}
{{ form.permissions.label_tag }}
{{ form.permissions }}
</div>
<input type="submit" value="Enregistrer">
</form>
<script type="text/javascript">
$(document).ready(function() {
$("select").multipleSelect({
width: 500,
filter: true,
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,80 @@
{% extends 'kfet/base.html' %}
{% block title %}Comptes en négatifs{% endblock %}
{% block content-header-title %}Comptes - Négatifs{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
<div class="content-left-top">
<div class="line line-big">{{ negatives|length }}</div>
<div class="line line-bigsub">compte{{ negatives|length|pluralize }} en négatif</div>
<div class="block">
<div class="line"><b>Total:</b> {{ negatives_sum|floatformat:2 }}€</div>
</div>
<div class="block">
<div class="line"><b>Découvert autorisé par défaut</b></div>
<div class="line">Montant: {{ settings.overdraft_amount }}€</div>
<div class="line">Pendant: {{ settings.overdraft_duration }}</div>
</div>
</div>
{% if perms.kfet.change_settings %}
<div class="buttons">
<a class="btn btn-primary btn-lg" href="{% url 'kfet.settings' %}">Modifier les valeurs par défaut</a>
</div>
{% endif %}
</div>
</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">
<h2>Liste des comptes en négatifs</h2>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<td></td>
<td>Tri</td>
<td>Nom</td>
<td>Balance</td>
<td>Réelle</td>
<td>Début</td>
<td>Découvert autorisé</td>
<td>Jusqu'au</td>
<td>Balance offset</td>
</tr>
</thead>
<tbody>
{% for neg in negatives %}
<tr>
<td class="text-center">
<a href="{% url 'kfet.account.update' neg.account.trigramme %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</td>
<td>{{ neg.account.trigramme }}</td>
<td>{{ neg.account.name }}</td>
<td class="text-right">{{ neg.account.balance|floatformat:2 }}€</td>
<td class="text-right">
{% if neg.account.balance_offset %}
{{ neg.account.real_balance|floatformat:2 }}€
{% endif %}
</td>
<td>{{ neg.start|date:'d/m/Y H:i:s'}}</td>
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
<td>{{ neg.authz_overdrafy_until|default_if_none:'' }}</td>
<td>{{ neg.balance_offset|default_if_none:'' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,95 @@
{% extends "kfet/base.html" %}
{% load staticfiles %}
{% load kfet_tags %}
{% load l10n %}
{% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.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-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>
{% endblock %}
{% block title %}
{% if account.user == request.user %}
Mon compte
{% else %}
Informations du compte {{ account.trigramme }}
{% endif %}
{% endblock %}
{% block content-header-title %}
{% if account.user == request.user %}
Mon compte
{% else %}
Compte - {{ account.trigramme }}
{% endif %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
{% include 'kfet/left_account.html' %}
</div>
</div>
<div class="col-sm-8 col-md-9 col-content-right">
{% include "kfet/base_messages.html" %}
<div class="content-right">
{% if addcosts %}
<div class="content-right-block">
<h2>Gagné des majorations</h2>
<div>
<ul>
{% for addcost in addcosts %}
<li>{{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="content-right-block">
<h2>Historique</h2>
<div id="history">
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ settings.subvention_cof|unlocalize }})}
khistory = new KHistory({
display_trigramme: false,
});
function getHistory() {
var data = {
'accounts': [{{ account.pk }}],
}
$.ajax({
dataType: "json",
url : "{% url 'kfet.history.json' %}",
method : "POST",
data : data,
})
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
khistory.addOpeGroup(data['opegroups'][i]);
}
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes);
});
}
getHistory();
});
</script>
{% endblock %}

View file

@ -0,0 +1,5 @@
{% if account.user == request.user %}
Mon compte
{% else %}
Informations du compte {{ account.trigramme }}
{% endif %}

View file

@ -0,0 +1,61 @@
{% extends "kfet/base.html" %}
{% load widget_tweaks %}
{% load staticfiles %}
{% block extra_head %}
{{ negative_form.media }}
{% endblock %}
{% block title %}
{% if account.user == request.user %}
Modification de mes informations
{% else %}
{{ account.trigramme }} - Édition
{% endif %}
{% endblock %}
{% block content-header-title %}
{% if account.user == request.user %}
Modification de mes informations
{% else %}
Édition du compte {{ account.trigramme }}
{% endif %}
{% endblock %}
{% block content %}
{% 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 action="{% url 'kfet.account.update' account.trigramme %}" method="post" class="form-horizontal">
{% csrf_token %}
{% include 'kfet/form_snippet.html' with form=user_form %}
{% include 'kfet/form_snippet.html' with form=cof_form %}
{% include 'kfet/form_snippet.html' with form=account_form %}
{% include 'kfet/form_snippet.html' with form=group_form %}
{% include 'kfet/form_snippet.html' with form=pwd_form %}
{% include 'kfet/form_snippet.html' with form=negative_form %}
{% if perms.kfet.is_team %}
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
</form>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#id_authz_overdraft_until').datetimepicker({
format: 'YYYY-MM-DD HH:mm',
stepping: 5,
locale: 'fr',
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,67 @@
{% extends 'kfet/base.html' %}
{% block title %}Articles{% endblock %}
{% block content-header-title %}Articles{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
<div class="content-left-top">
<div class="line line-big">{{ articles|length }}</div>
<div class="line line-bigsub">article{{ articles|length|pluralize }}</div>
</div>
<div class="buttons">
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.create' %}">
Nouvel article
</a>
</div>
</div>
</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">
<h2>Liste des articles</h2>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<td></td>
<td>Nom</td>
<td class="text-right">Prix</td>
<td class="text-right">Stock</td>
<td class="text-right">En vente</td>
<td class="text-right">Dernier inventaire</td>
</tr>
</thead>
<tbody>
{% for article in articles %}
{% ifchanged article.category %}
<tr class="section">
<td colspan="6">{{ article.category.name }}</td>
</tr>
{% endifchanged %}
<tr>
<td class="text-center">
<a href="{% url 'kfet.article.read' article.pk %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</td>
<td>{{ article.name }}</td>
<td class="text-right">{{ article.price }}€</td>
<td class="text-right">{{ article.stock }}</td>
<td class="text-right">{{ article.is_sold }}</td>
<td class="text-right">{{ article.inventory.0.at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends 'kfet/base.html' %}
{% 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>
{% endblock %}

View file

@ -0,0 +1,85 @@
{% extends 'kfet/base.html' %}
{% block title %}Informations sur l'article {{ article }}{% endblock %}
{% block content-header-title %}Article - {{ article.name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
<div class="content-left">
<div class="content-left-top">
<div class="line line-big">{{ article.name }}</div>
<div class="line line-bigsub">{{ article.category }}</div>
<div class="block">
<div class="line">Prix (hors réduc.): {{ article.price }}€</div>
<div class="line">Stock: {{ article.stock }}</div>
<div class="line">En vente: {{ article.is_sold }}</div>
</div>
</div>
<div class="buttons">
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.update' article.pk %}">
Modifier
</a>
</div>
</div>
</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">
<h2>Historique</h2>
<div class="row">
<div class="col-sm-6">
<h3>Inventaires</h3>
<table class="table">
<thead>
<tr>
<td>Date</td>
<td>Stock</td>
<td>Erreur</td>
</tr>
</thead>
<tbody>
{% for inventoryart in inventoryarts %}
<tr>
<td>{{ inventoryart.inventory.at }}</td>
<td>{{ inventoryart.stock_new }}</td>
<td>{{ inventoryart.stock_error }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-sm-6">
<h3>Prix fournisseurs</h3>
<table class="table">
<thead>
<tr>
<td>Date</td>
<td>Fournisseur</td>
<td>HT</td>
<td>TVA</td>
<td>Droits</td>
</tr>
</thead>
<tbody>
{% for supplierart in supplierarts %}
<tr>
<td>{{ supplierart.at }}</td>
<td>{{ supplierart.supplier.name }}</td>
<td>{{ supplierart.price_HT }}</td>
<td>{{ supplierart.TVA }}</td>
<td>{{ supplierart.rights }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show more