From 43d938edd08d0dc0343bf3177d489d9e70022824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 2 Aug 2016 10:40:46 +0200 Subject: [PATCH 001/192] initial --- cof/settings_dev.py | 1 + cof/urls.py | 1 + kfet/__init__.py | 0 kfet/admin.py | 3 + kfet/autocomplete.py | 52 +++ kfet/forms.py | 36 ++ kfet/migrations/0001_initial.py | 257 +++++++++++++ kfet/migrations/__init__.py | 0 kfet/models.py | 348 ++++++++++++++++++ kfet/templates/kfet/account_new.html | 74 ++++ .../kfet/account_new_autocomplete.html | 43 +++ kfet/templates/kfet/account_new_form.html | 5 + kfet/templates/kfet/base.html | 30 ++ kfet/templates/kfet/base_footer.html | 0 kfet/templatetags/__init__.py | 0 kfet/templatetags/kfet_tags.py | 28 ++ kfet/tests.py | 3 + kfet/urls.py | 21 ++ kfet/views.py | 150 ++++++++ requirements.txt | 1 + 20 files changed, 1053 insertions(+) create mode 100644 kfet/__init__.py create mode 100644 kfet/admin.py create mode 100644 kfet/autocomplete.py create mode 100644 kfet/forms.py create mode 100644 kfet/migrations/0001_initial.py create mode 100644 kfet/migrations/__init__.py create mode 100644 kfet/models.py create mode 100644 kfet/templates/kfet/account_new.html create mode 100644 kfet/templates/kfet/account_new_autocomplete.html create mode 100644 kfet/templates/kfet/account_new_form.html create mode 100644 kfet/templates/kfet/base.html create mode 100644 kfet/templates/kfet/base_footer.html create mode 100644 kfet/templatetags/__init__.py create mode 100644 kfet/templatetags/kfet_tags.py create mode 100644 kfet/tests.py create mode 100644 kfet/urls.py create mode 100644 kfet/views.py diff --git a/cof/settings_dev.py b/cof/settings_dev.py index ee777e1c..ad2b0b64 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -50,6 +50,7 @@ INSTALLED_APPS = ( 'django_cas_ng', 'debug_toolbar', 'bootstrapform', + 'kfet', ) MIDDLEWARE_CLASSES = ( diff --git a/cof/urls.py b/cof/urls.py index 5a4a49dc..64af9eda 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -78,6 +78,7 @@ urlpatterns = [ url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff), url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof), 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) if settings.DEBUG diff --git a/kfet/__init__.py b/kfet/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/admin.py b/kfet/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/kfet/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py new file mode 100644 index 00000000..70293475 --- /dev/null +++ b/kfet/autocomplete.py @@ -0,0 +1,52 @@ +from django.shortcuts import render +from django.http import Http404 +from django.db.models import Q +from gestioncof.models import User, Clipper + +def account_new(request): + if "q" not in request.GET: + raise Http404 + q = request.GET.get("q") + + if (len(q) == 0): + return render(request, "kfet/account_new_autocomplete.html") + + data = {'q': q} + + queries = {} + search_words = q.split() + + 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['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['users_cof'] = queries['users_cof'].distinct() + queries['users_notcof'] = queries['users_notcof'].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_new_autocomplete.html", data) diff --git a/kfet/forms.py b/kfet/forms.py new file mode 100644 index 00000000..2173e5a2 --- /dev/null +++ b/kfet/forms.py @@ -0,0 +1,36 @@ +from django import forms +from django.contrib.auth.models import User +from kfet.models import Account +from gestioncof.models import CofProfile + +class AccountTrigrammeForm(forms.ModelForm): + class Meta: + model = Account + fields = ['trigramme'] + widgets = { + 'trigramme': forms.TextInput(attrs={'autocomplete': 'off'}), + } + +class AccountForm(forms.ModelForm): + class Meta: + model = Account + fields = ['promo', 'nickname'] + +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 UserForm(forms.ModelForm): + class Meta: + model = User + fields = ['username', 'first_name', 'last_name', 'email'] + help_texts = { + 'username': '' + } diff --git a/kfet/migrations/0001_initial.py b/kfet/migrations/0001_initial.py new file mode 100644 index 00000000..4bc9ccdb --- /dev/null +++ b/kfet/migrations/0001_initial.py @@ -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_auto_20160802_1022'), + ] + + 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'), + ), + ] diff --git a/kfet/migrations/__init__.py b/kfet/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/models.py b/kfet/models.py new file mode 100644 index 00000000..2b2750ab --- /dev/null +++ b/kfet/models.py @@ -0,0 +1,348 @@ +from django.db import models +from django.core.exceptions import PermissionDenied +from django.contrib.auth.models import User +from django.core.validators import RegexValidator +from gestioncof.models import CofProfile +from django.utils.six.moves import reduce +import datetime + +def choices_length(choices): + return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) + +def default_promo(): + now = datetime.date.today() + return now.month <= 8 and now.year-1 or now.year + +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}$')]) + balance = models.DecimalField( + max_digits = 6, decimal_places = 2, + default = 0) + frozen = models.BooleanField(default = False) + # Optional + PROMO_CHOICES = [(r,r) for r in range(1980, datetime.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 self.trigramme + + @staticmethod + def is_free(trigramme): + try: + account = Account.objects.filter(trigramme=trigramme).get() + return False + except Account.DoesNotExist: + return True + + # Méthode save() avec auth + + # Args: + # - auth_user : request.user + # - data : datas pour User et CofProfile + # Action: + # - Enregistre User, CofProfile à partir de "data" + # - Enregistre Account + # Permissions + # - Edition si request.user: + # - modifie son compte (ne peut pas modifier nickname) + # ou - a la perm kfet.change_account + # - Ajout si request.user a la perm kfet.add_account + def save_api(self, auth_user, data = None): + if self.pk: + # Account update + + # Checking permissions + user = self.cofprofile.user + if not auth_user.has_perm('kfet.change_account') \ + and request.user != user: + raise PermissionDenied + + # Updating User with data + 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() + # Nickname is not editable by the user + if not auth_user.has_perm('kfet.change_account'): + account_old = Account.objects.get(pk=self.pk) + self.nickname = account_old.nickname + else: + # New account + + # Checking permissions + if not auth_user.has_perm('kfet.add_account'): + raise PermissionDenied + + # Creating or updating User instance + username = data.get("username") + (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() + # Check if cof is linked to an account + if hasattr(cof, 'account_kfet'): + raise Account.UserHasAccount(cof.account_kfet.trigramme) + self.cofprofile = cof + self.save() + + # 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(default = datetime.datetime.now()) + balance_offset = models.DecimalField(max_digits = 6, decimal_places = 2) + authorized_overdraft = models.DecimalField( + max_digits = 6, decimal_places = 2, + default = 0) + comment = models.CharField(max_length = 255, blank = True) + +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) + is_protected = models.BooleanField(default = False) + +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) + +class Statement(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) + +class ArticleCategory(models.Model): + name = models.CharField(max_length = 45) + +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) + stock = models.IntegerField(default = 0) + category = models.ForeignKey( + ArticleCategory, on_delete = models.PROTECT, + related_name = "articles") + +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 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) + +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() + +class SupplierArticle(models.Model): + supplier = models.ForeignKey( + Supplier, on_delete = models.PROTECT) + article = models.ForeignKey( + Article, on_delete = models.PROTECT) + 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)) + box_capacity = models.PositiveSmallIntegerField() + price_HT = models.DecimalField(max_digits = 7, decimal_places = 4) + TVA = models.DecimalField(max_digits = 4, decimal_places = 2) + rights = models.DecimalField(max_digits = 7, decimal_places = 4) + +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) + +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() + +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 = "operations") + checkout = models.ForeignKey( + Checkout, on_delete = models.PROTECT, + related_name = "operations") + at = models.DateTimeField(auto_now_add = True) + amount = models.IntegerField() + 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 = True) + +class Operation(models.Model): + PURCHASE = 'purchase' + DEPOSIT = 'deposit' + WITHDRAW = 'withdraw' + + TYPE_ORDER_CHOICES = ( + (PURCHASE, 'Achat'), + (DEPOSIT, 'Charge'), + (WITHDRAW, 'Retrait'), + ) + + group = models.ForeignKey( + OperationGroup, on_delete = models.PROTECT, + related_name = "+") + type = models.CharField( + choices = TYPE_ORDER_CHOICES, + max_length = choices_length(TYPE_ORDER_CHOICES)) + amount = models.DecimalField(max_digits = 6, decimal_places = 2) + on_checkout = models.BooleanField(default = True) + # Optional + article = models.ForeignKey( + Article, on_delete = models.PROTECT, + related_name = "operations", + 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) diff --git a/kfet/templates/kfet/account_new.html b/kfet/templates/kfet/account_new.html new file mode 100644 index 00000000..b7b28d88 --- /dev/null +++ b/kfet/templates/kfet/account_new.html @@ -0,0 +1,74 @@ +{% extends "kfet/base.html" %} +{% load static %} + +{% block title %}Création d'un nouveau compte{% endblock %} + +{% block extra_head %} + + +{% endblock %} + +{% block content %} +

Création d'un nouveau compte

+ + {% if post %} + {% if success %} + Nouveau compte créé : {{ trigramme }} + {% else %} + Echec de la création du compte + {{ errors }} + {% endif %} +
+ {% endif %} + +
+ {{ account_trigramme_form }} +

+ +
+
+
+ +{% endblock %} diff --git a/kfet/templates/kfet/account_new_autocomplete.html b/kfet/templates/kfet/account_new_autocomplete.html new file mode 100644 index 00000000..21700838 --- /dev/null +++ b/kfet/templates/kfet/account_new_autocomplete.html @@ -0,0 +1,43 @@ +{% load kfet_tags %} + + diff --git a/kfet/templates/kfet/account_new_form.html b/kfet/templates/kfet/account_new_form.html new file mode 100644 index 00000000..92e44059 --- /dev/null +++ b/kfet/templates/kfet/account_new_form.html @@ -0,0 +1,5 @@ +{% csrf_token %} +{{ user_form }} +{{ cof_form }} +{{ account_form }} + diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html new file mode 100644 index 00000000..642f8e22 --- /dev/null +++ b/kfet/templates/kfet/base.html @@ -0,0 +1,30 @@ + + + + + {% block title %}{% endblock %} | K-Fêt - ENS Ulm + + {% block extra_head %}{% endblock %} + + {# Vieux IE pas comprendre HTML5 #} + + + + +
+ {% block content %}TGTG{% endblock %} +
+ {% include "kfet/base_footer.html" %} + + diff --git a/kfet/templates/kfet/base_footer.html b/kfet/templates/kfet/base_footer.html new file mode 100644 index 00000000..e69de29b diff --git a/kfet/templatetags/__init__.py b/kfet/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/templatetags/kfet_tags.py b/kfet/templatetags/kfet_tags.py new file mode 100644 index 00000000..38538d9b --- /dev/null +++ b/kfet/templatetags/kfet_tags.py @@ -0,0 +1,28 @@ +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +import re + +register = template.Library() + +def highlight_text(text, q): + q2 = "|".join(q.split()) + pattern = re.compile(r"(?P%s)" % q2, re.IGNORECASE) + return mark_safe(re.sub(pattern, r"\g", text)) + +@register.filter(is_safe=True) +def highlight_user(user, q): + if user.first_name and user.last_name: + text = "%s %s (%s)" % (user.first_name, user.last_name, user.username) + else: + text = user.username + return highlight_text(escape(text), q) + +@register.filter(is_safe=True) +def highlight_clipper(clipper, q): + if clipper.fullname: + text = "%s (%s)" % (clipper.fullname, clipper.username) + else: + text = clipper.username + return highlight_text(escape(text), q) + diff --git a/kfet/tests.py b/kfet/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/kfet/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/kfet/urls.py b/kfet/urls.py new file mode 100644 index 00000000..235ed716 --- /dev/null +++ b/kfet/urls.py @@ -0,0 +1,21 @@ +from django.conf.urls import url +from kfet import views +from kfet import autocomplete + +urlpatterns = [ + url(r'^$', views.home), + # Administration + url(r'^admin/account/new$', views.account_new, + name = 'kfet.admin.account.new'), + url(r'^admin/account/new/user/(?P.+)$', views.account_new_ajax, + name = 'kfet.admin.account.new.fromuser'), + url(r'^admin/account/new/clipper/(?P.+)$', views.account_new_ajax, + name = 'kfet.admin.account.new.fromclipper'), + url(r'^admin/account/new/empty$', views.account_new_ajax, + name = 'kfet.admin.account.new.empty'), + url(r'^admin/account/is_free$', views.account_is_free_ajax, + name = 'kfet.admin.account.is_free.ajax'), + # Autocomplete - Nouveau compte + url(r'^autocomplete/account_new$', autocomplete.account_new, + name = 'kfet.admin.account.new.autocomplete'), +] diff --git a/kfet/views.py b/kfet/views.py new file mode 100644 index 00000000..69545168 --- /dev/null +++ b/kfet/views.py @@ -0,0 +1,150 @@ +from django.shortcuts import render, get_object_or_404 +from django.core.exceptions import PermissionDenied +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.models import User +from django.http import HttpResponse, Http404 +import json +from gestioncof.models import CofProfile, Clipper +from kfet.models import Account +from kfet.forms import AccountTrigrammeForm, AccountForm, CofForm, UserForm + +@login_required +def home(request): + return render(request, "kfet/base.html") + +def put_cleaned_data_in_dict(dict, form): + for field in form.cleaned_data: + dict[field] = form.cleaned_data[field] + +@login_required +@permission_required('kfet.add_account') +def account_new(request): + # A envoyer au template + data_template = { + 'account_trigramme_form': AccountTrigrammeForm(), + 'post' : False, + 'success' : False, + 'trigramme' : '', + 'errors' : {}, + } + + # Enregistrement + if request.method == "POST": + # Pour indiquer la tentative d'enregistrement au template + data_template['post'] = True + + # Peuplement des forms + username = request.POST.get('username') + try: + user = User.objects.filter(username=username).get() + (cof, _) = CofProfile.objects.get_or_create(user=user) + user_form = UserForm(request.POST, instance=user) + cof_form = CofForm(request.POST, instance=cof) + except User.DoesNotExist: + user_form = UserForm(request.POST) + cof_form = CofForm(request.POST) + trigramme_form = AccountTrigrammeForm(request.POST) + account_form = AccountForm(request.POST) + # Ajout des erreurs pour le template + data_template['errors']['user_form'] = user_form.errors + data_template['errors']['cof_form'] = cof_form.errors + data_template['errors']['trigramme_form'] = trigramme_form.errors + data_template['errors']['account_form'] = account_form.errors + + if all((user_form.is_valid(), cof_form.is_valid(), + trigramme_form.is_valid(), account_form.is_valid())): + print(user_form.cleaned_data) + data = {} + # Fill data + put_cleaned_data_in_dict(data, user_form) + put_cleaned_data_in_dict(data, cof_form) + + account = Account() + account.trigramme = trigramme_form.cleaned_data['trigramme'] + account.promo = account_form.cleaned_data['promo'] + account.nickname = account_form.cleaned_data['nickname'] + try: + account.save_api(auth_user = request.user, data = data) + data_template['success'] = True + data_template['trigramme'] = account.trigramme + except Account.UserHasAccount as e: + data_template['errors']['global'] = \ + "Cet utilisateur a déjà un compte K-Fêt : " + e.trigramme + except PermissionDenied: + print("perm") + + return render(request, "kfet/account_new.html", data_template) + +def account_new_set_readonly_fields(user_form, cof_form): + user_form.fields['username'].widget.attrs['readonly'] = True + cof_form.fields['login_clipper'].widget.attrs['readonly'] = True + cof_form.fields['is_cof'].widget.attrs['disabled'] = True + +def account_new_ajax(request, username=None, login_clipper=None): + user = None + if login_clipper: + # à partir d'un clipper + # le user associé à ce clipper ne devrait pas encore existé + clipper = get_object_or_404(Clipper, username = login_clipper) + try: + # Vérification que clipper ne soit pas déjà dans User + user = User.objects.filter(username=login_clipper).get() + # Ici, on nous a menti, le user existe déjà + username = user.username + login_clipper = None + except User.DoesNotExist: + # Clipper (sans user déjà existant) + + # UserForm - Prefill + Création + user_initial_data = { + 'username' : login_clipper, + 'email' : login_clipper + "@clipper.ens.fr"} + if clipper.fullname: + # Prefill du nom et prénom + names = clipper.fullname.split() + # Le premier, c'est le prénom + user_initial_data['first_name'] = names[0] + if len(names) > 1: + # Si d'autres noms -> tous dans le nom de famille + user_initial_data['last_name'] = " ".join(names[1:]) + user_form = UserForm(initial = user_initial_data) + + # CofForm - Prefill + Création + cof_initial_data = { 'login_clipper': login_clipper } + cof_form = CofForm(initial = cof_initial_data) + + # AccountForm + account_form = AccountForm() + + # Protection (read-only) des champs username et login_clipper + account_new_set_readonly_fields(user_form, cof_form) + if username: + # le user existe déjà + user = get_object_or_404(User, username=username) + # récupération du profil cof + (cof, _) = CofProfile.objects.get_or_create(user=user) + # UserForm + CofForm - Création à partir des instances existantes + user_form = UserForm(instance = user) + cof_form = CofForm(instance = cof) + # AccountForm + account_form = AccountForm() + # Protection (read-only) des champs username et login_clipper + account_new_set_readonly_fields(user_form, cof_form) + elif not login_clipper: + # connaît pas du tout, faut tout remplir + user_form = UserForm() + cof_form = CofForm() + account_form = AccountForm() + + return render(request, "kfet/account_new_form.html", { + 'account_form' : account_form, + 'cof_form' : cof_form, + 'user_form' : user_form, + }) + +def account_is_free_ajax(request): + if not request.GET.get("trigramme"): + raise Http404 + trigramme = request.GET.get("trigramme") + data = { 'is_free': Account.is_free(trigramme) } + return HttpResponse(json.dumps(data), content_type = 'application/json') diff --git a/requirements.txt b/requirements.txt index c1831e33..140f5f67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ six==1.10.0 unicodecsv==0.14.1 icalendar==3.10 django-bootstrap-form==3.2.1 +django-rest-framework==3.4.0 From b5260882c1a3fc87087c09814ac87d9b6a686770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Wed, 3 Aug 2016 04:38:54 +0200 Subject: [PATCH 002/192] =?UTF-8?q?Gestion=20tr=C3=A8s=20primaire=20des=20?= =?UTF-8?q?utilisateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/autocomplete.py | 47 ++++-- kfet/forms.py | 34 ++++- kfet/migrations/0002_auto_20160802_2139.py | 24 ++++ kfet/migrations/0003_auto_20160802_2142.py | 20 +++ kfet/migrations/0004_auto_20160802_2144.py | 19 +++ kfet/migrations/0005_auto_20160802_2154.py | 28 ++++ kfet/models.py | 72 ++++++---- kfet/templates/kfet/account.html | 17 +++ kfet/templates/kfet/account_create.html | 82 +++++++++++ ....html => account_create_autocomplete.html} | 14 +- kfet/templates/kfet/account_create_form.html | 5 + kfet/templates/kfet/account_new.html | 74 ---------- kfet/templates/kfet/account_new_form.html | 5 - kfet/templates/kfet/account_read.html | 40 ++++++ kfet/templates/kfet/account_update.html | 26 ++++ kfet/templates/kfet/base.html | 4 +- kfet/urls.py | 46 ++++-- kfet/views.py | 135 ++++++++++++++---- 18 files changed, 518 insertions(+), 174 deletions(-) create mode 100644 kfet/migrations/0002_auto_20160802_2139.py create mode 100644 kfet/migrations/0003_auto_20160802_2142.py create mode 100644 kfet/migrations/0004_auto_20160802_2144.py create mode 100644 kfet/migrations/0005_auto_20160802_2154.py create mode 100644 kfet/templates/kfet/account.html create mode 100644 kfet/templates/kfet/account_create.html rename kfet/templates/kfet/{account_new_autocomplete.html => account_create_autocomplete.html} (65%) create mode 100644 kfet/templates/kfet/account_create_form.html delete mode 100644 kfet/templates/kfet/account_new.html delete mode 100644 kfet/templates/kfet/account_new_form.html create mode 100644 kfet/templates/kfet/account_read.html create mode 100644 kfet/templates/kfet/account_update.html diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py index 70293475..eaee1fbd 100644 --- a/kfet/autocomplete.py +++ b/kfet/autocomplete.py @@ -2,43 +2,66 @@ 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_new(request): +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_new_autocomplete.html") + 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['users_cof'] = queries['users_cof'].filter( + 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)) + | 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)) + | Q(last_name__icontains = word) + ) queries['clippers'] = queries['clippers'].filter( Q(username__icontains = word) - | Q(fullname__icontains = word)) + | Q(fullname__icontains = word) + ) - queries['users_cof'] = queries['users_cof'].distinct() - queries['users_notcof'] = queries['users_notcof'].distinct() + queries['kfet'] = queries['kfet'].distinct() + print(queries['kfet']) + + 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)) - 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() @@ -49,4 +72,4 @@ def account_new(request): options += len(query) data['options'] = options - return render(request, "kfet/account_new_autocomplete.html", data) + return render(request, "kfet/account_create_autocomplete.html", data) diff --git a/kfet/forms.py b/kfet/forms.py index 2173e5a2..5328a5f2 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -3,18 +3,32 @@ from django.contrib.auth.models import User from kfet.models import Account from gestioncof.models import CofProfile -class AccountTrigrammeForm(forms.ModelForm): +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'] + fields = ['trigramme', 'promo', 'nickname'] widgets = { 'trigramme': forms.TextInput(attrs={'autocomplete': 'off'}), } -class AccountForm(forms.ModelForm): - class Meta: - model = Account - fields = ['promo', 'nickname'] +class AccountTriForm(AccountForm): + class Meta(AccountForm.Meta): + fields = ['trigramme'] + +class AccountNoTriForm(AccountForm): + class Meta(AccountForm.Meta): + exclude = ['trigramme'] + +class AccountRestrictForm(AccountForm): + class Meta(AccountForm.Meta): + fields = ['promo'] class CofForm(forms.ModelForm): def clean_is_cof(self): @@ -27,6 +41,10 @@ class CofForm(forms.ModelForm): model = CofProfile fields = ['login_clipper', 'is_cof', 'departement'] +class CofRestrictForm(CofForm): + class Meta(CofForm.Meta): + fields = ['departement'] + class UserForm(forms.ModelForm): class Meta: model = User @@ -34,3 +52,7 @@ class UserForm(forms.ModelForm): help_texts = { 'username': '' } + +class UserRestrictForm(UserForm): + class Meta(UserForm.Meta): + fields = ['first_name', 'last_name', 'email'] diff --git a/kfet/migrations/0002_auto_20160802_2139.py b/kfet/migrations/0002_auto_20160802_2139.py new file mode 100644 index 00000000..0a59de44 --- /dev/null +++ b/kfet/migrations/0002_auto_20160802_2139.py @@ -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)), + ), + ] diff --git a/kfet/migrations/0003_auto_20160802_2142.py b/kfet/migrations/0003_auto_20160802_2142.py new file mode 100644 index 00000000..586146de --- /dev/null +++ b/kfet/migrations/0003_auto_20160802_2142.py @@ -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), + ), + ] diff --git a/kfet/migrations/0004_auto_20160802_2144.py b/kfet/migrations/0004_auto_20160802_2144.py new file mode 100644 index 00000000..b9e9d0d3 --- /dev/null +++ b/kfet/migrations/0004_auto_20160802_2144.py @@ -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), + ), + ] diff --git a/kfet/migrations/0005_auto_20160802_2154.py b/kfet/migrations/0005_auto_20160802_2154.py new file mode 100644 index 00000000..a01419fc --- /dev/null +++ b/kfet/migrations/0005_auto_20160802_2154.py @@ -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={}, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 2b2750ab..76310337 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -1,10 +1,11 @@ from django.db import models from django.core.exceptions import PermissionDenied -from django.contrib.auth.models import User +from django.contrib.auth.models import User, AnonymousUser from django.core.validators import RegexValidator from gestioncof.models import CofProfile from django.utils.six.moves import reduce import datetime +import re def choices_length(choices): return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) @@ -42,36 +43,36 @@ class Account(models.Model): return self.trigramme @staticmethod - def is_free(trigramme): + 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.filter(trigramme=trigramme).get() - return False + account = Account.objects.get(trigramme=trigramme) except Account.DoesNotExist: - return True + data['is_free'] = True + return data - # Méthode save() avec auth + + def real_balance(self): + if (hasattr(self, 'negative')): + return self.balance + self.negative.balance_offset + return self.balance + + def read(self, auth_user = AnonymousUser()): + user = self.cofprofile.user + + # Surcharge Méthode save() avec gestions de User et CofProfile # Args: - # - auth_user : request.user # - data : datas pour User et CofProfile # Action: # - Enregistre User, CofProfile à partir de "data" # - Enregistre Account - # Permissions - # - Edition si request.user: - # - modifie son compte (ne peut pas modifier nickname) - # ou - a la perm kfet.change_account - # - Ajout si request.user a la perm kfet.add_account - def save_api(self, auth_user, data = None): + def save(self, data = {}, *args, **kwargs): if self.pk: # Account update - # Checking permissions - user = self.cofprofile.user - if not auth_user.has_perm('kfet.change_account') \ - and request.user != user: - raise PermissionDenied - # Updating User with data + user = self.cofprofile.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) @@ -81,18 +82,25 @@ class Account(models.Model): cof.departement = data.get("departement", cof.departement) cof.save() # Nickname is not editable by the user + """ if not auth_user.has_perm('kfet.change_account'): account_old = Account.objects.get(pk=self.pk) self.nickname = account_old.nickname + """ else: # New account - # Checking permissions - if not auth_user.has_perm('kfet.add_account'): - raise PermissionDenied + # 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 - username = data.get("username") (user, _) = User.objects.get_or_create(username=username) if "first_name" in data: user.first_name = data['first_name'] @@ -108,11 +116,8 @@ class Account(models.Model): if "departement" in data: cof.departement = data['departement'] cof.save() - # Check if cof is linked to an account - if hasattr(cof, 'account_kfet'): - raise Account.UserHasAccount(cof.account_kfet.trigramme) self.cofprofile = cof - self.save() + super(Account, self).save(*args, **kwargs) # Surcharge de delete # Pas de suppression possible @@ -128,8 +133,10 @@ class AccountNegative(models.Model): account = models.OneToOneField( Account, on_delete = models.PROTECT, related_name = "negative") - start = models.DateTimeField(default = datetime.datetime.now()) - balance_offset = models.DecimalField(max_digits = 6, decimal_places = 2) + start = models.DateTimeField(default = datetime.datetime.now) + balance_offset = models.DecimalField( + max_digits = 6, decimal_places = 2, + default = 0) authorized_overdraft = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0) @@ -346,3 +353,10 @@ class Operation(models.Model): related_name = "addcosts", blank = True, null = True, default = None) addcost_amount = models.DecimalField(max_digits = 6, decimal_places = 2) + +class GlobalPermissions(models.Model): + class Meta: + managed = False + permissions = ( + ('is_team', 'Is part of the team'), + ) diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html new file mode 100644 index 00000000..450d6a94 --- /dev/null +++ b/kfet/templates/kfet/account.html @@ -0,0 +1,17 @@ +{% extends "kfet/base.html" %} + +{% block title %}Liste des comptes{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/kfet/templates/kfet/account_create.html b/kfet/templates/kfet/account_create.html new file mode 100644 index 00000000..a130c9c6 --- /dev/null +++ b/kfet/templates/kfet/account_create.html @@ -0,0 +1,82 @@ +{% extends "kfet/base.html" %} +{% load static %} + +{% block title %}Création d'un nouveau compte{% endblock %} + +{% block extra_head %} + + +{% endblock %} + +{% block content %} +

Création d'un nouveau compte

+ +{% if post %} + {% if success %} + Nouveau compte créé : {{ trigramme }} + {% else %} + Echec de la création du compte + {{ errors }} + {% endif %} +
+{% endif %} + +
+ {{ account_trigramme_form }} +
+ +
+
+
+ + +{% endblock %} diff --git a/kfet/templates/kfet/account_new_autocomplete.html b/kfet/templates/kfet/account_create_autocomplete.html similarity index 65% rename from kfet/templates/kfet/account_new_autocomplete.html rename to kfet/templates/kfet/account_create_autocomplete.html index 21700838..70a75f34 100644 --- a/kfet/templates/kfet/account_new_autocomplete.html +++ b/kfet/templates/kfet/account_create_autocomplete.html @@ -2,15 +2,21 @@ {% endif %} + {% if request.user.is_authenticated %} +
  • + {% endif %} + diff --git a/kfet/views.py b/kfet/views.py index d6e6d060..662de4b7 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -22,6 +22,7 @@ from collections import defaultdict from channels import Group from kfet import consumers from datetime import timedelta +import django_cas_ng @login_required def home(request): @@ -29,10 +30,18 @@ def home(request): @permission_required('kfet.is_team') def login_genericteam(request): + profile, _ = CofProfile.objects.get_or_create(user=request.user) + logout_cas = '' + if profile.login_clipper: + logout_cas = django_cas_ng.views.logout(request) + token = GenericTeamToken.objects.create(token=get_random_string(50)) user = authenticate(username="kfet_genericteam", token=token.token) login(request, user) - print(request.user) + + if logout_cas: + return logout_cas + return render(request, "kfet/login_genericteam.html") def put_cleaned_data_in_dict(dict, form): From 25dd34e402621e5d6a4157dc765e18682b1372c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 20 Aug 2016 21:15:14 +0200 Subject: [PATCH 072/192] Correction annulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chrome ne voulait toujours pas de `keypress` sur `$(document)` -> Corrigé avec un `keydown` --- kfet/templates/kfet/kpsul.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 946ac6b2..46598cee 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1039,7 +1039,7 @@ $(document).ready(function() { } }); - $(document).on('keypress', function (e) { + $(document).on('keydown', function (e) { if (e.keyCode == 46) { // DEL (Suppr) var opes_to_cancel = []; From ac61a6e5c6089e7ea0f23de4afe79772050888dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 20 Aug 2016 21:22:24 +0200 Subject: [PATCH 073/192] =?UTF-8?q?Correction=20s=C3=A9lection=20annulatio?= =?UTF-8?q?n(s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/models.py | 2 ++ kfet/templates/kfet/kpsul.html | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index cbac30f2..fbda4db4 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -118,6 +118,8 @@ class Account(models.Model): perms.add('kfet.override_frozen_protection') new_balance = self.balance + amount if new_balance < 0 and amount < 0: + print(new_balance) + print(amount) # Retrieving overdraft amount limit if (hasattr(self, 'negative') and self.negative.authz_overdraft_amount is not None): diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 46598cee..c4fddb27 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1028,15 +1028,10 @@ $(document).ready(function() { selected: function(e, ui) { $(ui.selected).each(function() { if ($(this).hasClass('opegroup')) - $(this).siblings('.ope').addClass('ui-selected'); + var opegroup = $(this).attr('data-opegroup'); + $(this).siblings('.ope[data-opegroup='+opegroup+']').addClass('ui-selected'); }); }, - unselected: function(e, ui) { - $(ui.unselected).each(function() { - if ($(this).hasClass('opegroup')) - $(this).siblings('.ope').removeClass('ui-selected'); - }); - } }); $(document).on('keydown', function (e) { From 8507072c8f2429507f936b7f320a09170e8509d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 20 Aug 2016 23:31:30 +0200 Subject: [PATCH 074/192] =?UTF-8?q?Auth=20sp=C3=A9ciale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Le backend d'auth K-Fêt est étendu pour aussi identifier une personne dans le cas dans d'un formulaire en récupérant le password contenu dans l'input de nom `KFETPASSWORD` - Le middleware d'auth K-Fêt enregistre l'utilisateur connecté de manière normale dans `request.real_user` - Ajout d'un processeurs de contextes `kfet.context_processors.auth` qui qui remplace `user` et `perms` par l'utilisateur connecté de manière normale (`request.real_user`) et non celui connecté temporairement - Modification de la vue de modif d'un compte pour s'adapter à l'auth - Modification du template de modification d'un compte pour utiliser ce moyen d'authentification - Séparation du JS conservant le côté gauche d'une page à l'écran - Séparation de l'encart gauche contenant les infos d'un comtpe dans un autre template (`left_account`) pour l'utiliser dans `account_read` et `account_update` - `base_nav` utilise user (qui est donc le vrai utilisateur connecté) au lieu de `request.user` qui peut aussi bien être le vrai utilisateur qu'un utilisateur temporaire --- cof/settings_dev.py | 1 + kfet/backends.py | 3 +- kfet/context_processors.py | 9 ++++ kfet/middleware.py | 1 + kfet/static/kfet/css/nav.css | 5 +++ kfet/static/kfet/js/kfet.js | 12 +++++ kfet/templates/kfet/account_read.html | 59 +------------------------ kfet/templates/kfet/account_update.html | 36 ++++++++++----- kfet/templates/kfet/base.html | 1 + kfet/templates/kfet/base_nav.html | 8 ++-- kfet/templates/kfet/left_account.html | 43 ++++++++++++++++++ kfet/views.py | 54 +++++++++++----------- 12 files changed, 132 insertions(+), 100 deletions(-) create mode 100644 kfet/context_processors.py create mode 100644 kfet/static/kfet/js/kfet.js create mode 100644 kfet/templates/kfet/left_account.html diff --git a/cof/settings_dev.py b/cof/settings_dev.py index 717e36a0..86513843 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -83,6 +83,7 @@ TEMPLATES = [ 'django.core.context_processors.media', 'django.core.context_processors.static', 'gestioncof.shared.context_processor', + 'kfet.context_processors.auth', ], }, }, diff --git a/kfet/backends.py b/kfet/backends.py index 6ba9063a..4eea72b5 100644 --- a/kfet/backends.py +++ b/kfet/backends.py @@ -6,7 +6,8 @@ from kfet.models import Account, GenericTeamToken class KFetBackend(object): def authenticate(self, request): - password = request.META.get('HTTP_KFETPASSWORD') + password = request.POST.get('KFETPASSWORD', '') + password = request.META.get('HTTP_KFETPASSWORD', password) if not password: return None diff --git a/kfet/context_processors.py b/kfet/context_processors.py new file mode 100644 index 00000000..1bd8052c --- /dev/null +++ b/kfet/context_processors.py @@ -0,0 +1,9 @@ +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 {} diff --git a/kfet/middleware.py b/kfet/middleware.py index 13c01293..c6faf6c5 100644 --- a/kfet/middleware.py +++ b/kfet/middleware.py @@ -5,4 +5,5 @@ class KFetAuthenticationMiddleware(object): kfet_backend = KFetBackend() temp_request_user = kfet_backend.authenticate(request) if temp_request_user: + request.real_user = request.user request.user = temp_request_user diff --git a/kfet/static/kfet/css/nav.css b/kfet/static/kfet/css/nav.css index d89384b4..701f31e2 100644 --- a/kfet/static/kfet/css/nav.css +++ b/kfet/static/kfet/css/nav.css @@ -4,6 +4,11 @@ nav { font-family:Oswald; } +.navbar-nav > li > .dropdown-menu { + border:0; + border-radius:0; +} + .navbar-fixed-top { border:0; } diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js new file mode 100644 index 00000000..f9a4b040 --- /dev/null +++ b/kfet/static/kfet/js/kfet.js @@ -0,0 +1,12 @@ +$(document).ready(function() { + $(window).scroll(function() { + console.log($(this).scrollTop()); + 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'); + } + }); +}); diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index df2fd0ce..8cf405a2 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -22,49 +22,7 @@
    -
    -
    {{ account.trigramme }}
    -
    {{ account.balance|ukf:account.is_cof }} UKF
    -
    -
    {{ account.name }}
    - {% if perms.kfet.is_team %} -
    {{ account.nickname }}
    - {% endif %} -
    - {% if account.email %} - {{ account.email }} - {% else %} - Pas d'email ! - {% endif %} -
    -
    - {{ account.departement }} {{ account.promo }} -
    -
    Statut COF: {{ account.is_cof }}
    -
    -
    - {% if account.negative.start %} -
    En négatif depuis {{ account.negative.start }}
    - {% endif %} - {% if account.negative.balance_offset %} -
    Solde réel: {{ account.real_balance }} €
    - {% endif %} - {% if account.negative.authz_overdraft_amount %} -
    Découvert autorisé: {{ account.negative.authz_overdraft_amount }} €
    - {% endif %} - {% if account.negative.authz_overdraft_until %} -
    Découvert autorisé jusqu'à : {{ account.negative.authz_overdraft_until }}
    - {% endif %} -
    -
    - + {% include 'kfet/left_account.html' %}
    @@ -116,19 +74,4 @@
    - - {% endblock %} diff --git a/kfet/templates/kfet/account_update.html b/kfet/templates/kfet/account_update.html index bea92210..43bc7f19 100644 --- a/kfet/templates/kfet/account_update.html +++ b/kfet/templates/kfet/account_update.html @@ -18,17 +18,29 @@ {% block content %} -{% if post and success %} -

    Informations mises à jour

    -{% elif post and not success %} -

    Echec de la mise à jour des informations

    -{% endif %} -
    - {% csrf_token %} - {{ user_form.as_p }} - {{ cof_form.as_p }} - {{ account_form.as_p }} - -
    +
    +
    +
    + {% include 'kfet/left_account.html' %} +
    +
    +
    + {% include "kfet/base_messages.html" %} +
    +
    +
    + {% csrf_token %} + {{ user_form.as_p }} + {{ cof_form.as_p }} + {{ account_form.as_p }} + {% if perms.kfet.is_team and not perms.kfet.change_account %} + + {% endif %} + +
    +
    +
    +
    +
    {% endblock %} diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html index f7a016a0..22e4e9cb 100644 --- a/kfet/templates/kfet/base.html +++ b/kfet/templates/kfet/base.html @@ -17,6 +17,7 @@ + {% block extra_head %}{% endblock %} diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index 24ecbd7a..a4f3836c 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -18,10 +18,10 @@
  • Home
  • {% endif %} - {% if request.user.is_authenticated %} + {% if user.is_authenticated %}
  • {% endif %} diff --git a/kfet/templates/kfet/left_account.html b/kfet/templates/kfet/left_account.html new file mode 100644 index 00000000..e379adfb --- /dev/null +++ b/kfet/templates/kfet/left_account.html @@ -0,0 +1,43 @@ +{% load kfet_tags %} + +
    +
    {{ account.trigramme }}
    +
    {{ account.balance|ukf:account.is_cof }} UKF
    +
    +
    {{ account.name }}
    + {% if perms.kfet.is_team %} +
    {{ account.nickname }}
    + {% endif %} +
    + {% if account.email %} + {{ account.email }} + {% else %} + Pas d'email ! + {% endif %} +
    +
    + {{ account.departement }} {{ account.promo }} +
    +
    Statut COF: {{ account.is_cof }}
    +
    +
    + {% if account.negative.start %} +
    En négatif depuis {{ account.negative.start }}
    + {% endif %} + {% if account.negative.balance_offset %} +
    Solde réel: {{ account.real_balance }} €
    + {% endif %} + {% if account.negative.authz_overdraft_amount %} +
    Découvert autorisé: {{ account.negative.authz_overdraft_amount }} €
    + {% endif %} + {% if account.negative.authz_overdraft_until %} +
    Découvert autorisé jusqu'à : {{ account.negative.authz_overdraft_until }}
    + {% endif %} +
    +
    + diff --git a/kfet/views.py b/kfet/views.py index 662de4b7..0b6a03cf 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -230,11 +230,6 @@ def account_update(request, trigramme): if request.method == "POST": # Update attempt - # Checking permissions - if not request.user.has_perm('kfet.change_account') \ - and request.user != account.user: - raise PermissionDenied - # Peuplement des forms if request.user.has_perm('kfet.change_account'): account_form = AccountForm(request.POST, instance = account) @@ -243,24 +238,33 @@ def account_update(request, trigramme): cof_form = CofRestrictForm(request.POST, instance=account.cofprofile) user_form = UserRestrictForm(request.POST, instance=account.user) - if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())): - data = {} - # Fill data for Account.save() - put_cleaned_data_in_dict(data, user_form) - put_cleaned_data_in_dict(data, cof_form) + # Checking permissions + if (request.user.has_perm('kfet.change_account') + or request.user == account.user): + # Permissions ok + if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())): + data = {} + # Fill data for Account.save() + put_cleaned_data_in_dict(data, user_form) + put_cleaned_data_in_dict(data, cof_form) - # Updating - account_form.save(data = data) - if request.user == account.user: - messages.success(request, \ - 'Vos informations ont été mises à jour') + # Updating + account_form.save(data = data) + if request.user == account.user: + messages.success(request, + 'Vos informations ont été mises à jour') + else: + messages.success(request, + 'Informations du compte %s mises à jour' % account.trigramme) + #return redirect('kfet.account.read', account.trigramme) else: - messages.success(request, \ - 'Informations du compte %s mises à jour' % account.trigramme) - return redirect('kfet.account.read', account.trigramme) + messages.error(request, + 'Informations non mises à jour. Corrigez les erreurs') else: - messages.error(request, \ - 'Informations non mises à jour. Corrigez les erreurs') + # Permissions not ok + if request.user.has_perm('kfet.is_team'): + account_form = AccountForm(request.POST, instance = account) + messages.error(request, 'Permission refusée') else: # No update attempt if request.user.has_perm('kfet.is_team'): @@ -271,11 +275,11 @@ def account_update(request, trigramme): user_form = UserRestrictForm(instance = account.user) return render(request, "kfet/account_update.html", { - 'account' : account, - 'account_form' : account_form, - 'cof_form' : cof_form, - 'user_form' : user_form, - }) + 'account' : account, + 'account_form' : account_form, + 'cof_form' : cof_form, + 'user_form' : user_form, + }) # ----- # Checkout views From e64a443fb3d14ea78e47bd030b0ec49f749fc0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 21 Aug 2016 02:53:35 +0200 Subject: [PATCH 075/192] =?UTF-8?q?Ajout=20groupes=20K-F=C3=AAt=20utilisat?= =?UTF-8?q?eurs=20en=20lecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pour voir les groupes, il faut la permission `kfet.manage_perms` - Pour modifier les groupes auxquels fait parti un compte, il la faut également --- kfet/forms.py | 13 ++- kfet/migrations/0030_auto_20160821_0029.py | 18 ++++ kfet/models.py | 8 +- kfet/static/kfet/css/index.css | 28 +++++- kfet/templates/kfet/account.html | 39 +++++--- kfet/templates/kfet/account_group.html | 49 ++++++++++ kfet/templates/kfet/account_update.html | 1 + kfet/urls.py | 4 + kfet/views.py | 105 ++++++++++++--------- 9 files changed, 198 insertions(+), 67 deletions(-) create mode 100644 kfet/migrations/0030_auto_20160821_0029.py create mode 100644 kfet/templates/kfet/account_group.html diff --git a/kfet/forms.py b/kfet/forms.py index e6807884..8dcd00fc 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -1,6 +1,6 @@ from django import forms from django.core.exceptions import ValidationError -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group from django.forms import modelformset_factory from kfet.models import (Account, Checkout, Article, OperationGroup, Operation, CheckoutStatement) @@ -78,9 +78,20 @@ class UserForm(forms.ModelForm): } 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'] + # ----- # Checkout forms # ----- diff --git a/kfet/migrations/0030_auto_20160821_0029.py b/kfet/migrations/0030_auto_20160821_0029.py new file mode 100644 index 00000000..ed54efa9 --- /dev/null +++ b/kfet/migrations/0030_auto_20160821_0029.py @@ -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}, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index fbda4db4..7319e731 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -79,12 +79,7 @@ class Account(models.Model): @property def name(self): - if self.first_name and self.last_name: - return '%s %s' % (self.first_name, self.last_name) - elif self.first_name: - return self.first_name - else: - return self.last_name + return self.user.get_full_name() @property def is_cash(self): @@ -488,6 +483,7 @@ class GlobalPermissions(models.Model): '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') ) class Settings(models.Model): diff --git a/kfet/static/kfet/css/index.css b/kfet/static/kfet/css/index.css index d98453fd..fa08b10a 100644 --- a/kfet/static/kfet/css/index.css +++ b/kfet/static/kfet/css/index.css @@ -55,10 +55,6 @@ a:focus, a:hover { padding:0; } -/*.col-content-left { - position:fixed; -}*/ - .content-left-top { background:#fff; padding:10px 30px; @@ -102,6 +98,22 @@ a:focus, a:hover { margin:0 15px; } +.content-right-block { + padding-bottom:5px; +} + +.content-right-block:last-child { + padding-bottom:15px; +} + +.content-right-block > div { + background:#fff; +} + +.content-right-block > div.row { + margin:0; +} + .content-right-block h2 { margin:20px 20px 15px; padding-bottom:5px; @@ -109,6 +121,14 @@ a:focus, a:hover { 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; +} + .content-right-block table { width:100%; } diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html index d753b54e..71d9f350 100644 --- a/kfet/templates/kfet/account.html +++ b/kfet/templates/kfet/account.html @@ -6,21 +6,34 @@ {% block content %}
    -
    -
    - Créer un compte +
    +
    +
    +
    {{ accounts|length|add:-1 }} comptes
    +
    + +
    +
    +
    + {% include 'kfet/base_messages.html' %} +
    +
    +

    Liste des comptes

    +
    +
      + {% for account in accounts %} +
    • + {{ account }} +
    • + {% endfor %} +
    +
    +
    - - {% endblock %} diff --git a/kfet/templates/kfet/account_group.html b/kfet/templates/kfet/account_group.html new file mode 100644 index 00000000..ad371f6e --- /dev/null +++ b/kfet/templates/kfet/account_group.html @@ -0,0 +1,49 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Groupes de comptes{% endblock %} +{% block content-header-title %}Groupes de comptes{% endblock %} + +{% block content %} + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {% include 'kfet/base_messages.html' %} +
    + {% for group in groups %} +
    +

    {{ group.name }}

    +
    +
    +

    Permissions

    +
      + {% for perm in group.permissions.all %} +
    • {{ perm.name }}
    • + {% endfor %} +
    +
    +
    +

    Comptes

    +
      + {% for user in group.user_set.all %} +
    • {{ user.profile.account_kfet }}
    • + {% endfor %} +
    +
    +
    +
    + {% endfor %} +
    +
    +
    + +{% endblock %} diff --git a/kfet/templates/kfet/account_update.html b/kfet/templates/kfet/account_update.html index 43bc7f19..dedbcb24 100644 --- a/kfet/templates/kfet/account_update.html +++ b/kfet/templates/kfet/account_update.html @@ -33,6 +33,7 @@ {{ user_form.as_p }} {{ cof_form.as_p }} {{ account_form.as_p }} + {{ group_form.as_p }} {% if perms.kfet.is_team and not perms.kfet.change_account %} {% endif %} diff --git a/kfet/urls.py b/kfet/urls.py index 944ebcaa..df597766 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -39,6 +39,10 @@ urlpatterns = [ url(r'^accounts/(?P.{3})/edit$', views.account_update, name = 'kfet.account.update'), + # Account - Groups + url(r'^accounts/groups$', views.account_group, + name = 'kfet.account.group'), + # ----- # Checkout urls # ----- diff --git a/kfet/views.py b/kfet/views.py index 0b6a03cf..60dc3798 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -7,7 +7,7 @@ from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import login_required, permission_required -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import User, Permission, Group from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory from django.db import IntegrityError, transaction @@ -19,7 +19,6 @@ from kfet.models import (Account, Checkout, Article, Settings, AccountNegative, CheckoutStatement, GenericTeamToken) from kfet.forms import * from collections import defaultdict -from channels import Group from kfet import consumers from datetime import timedelta import django_cas_ng @@ -78,7 +77,6 @@ def account_create(request): # A envoyer au template data_template = { 'account_trigramme_form': AccountTriForm(), - 'errors' : {}, } # Enregistrement @@ -102,12 +100,6 @@ def account_create(request): trigramme_form = AccountTriForm(request.POST) account_form = AccountNoTriForm(request.POST) - # Ajout des erreurs pour le template - data_template['errors']['user_form'] = user_form.errors - data_template['errors']['cof_form'] = cof_form.errors - data_template['errors']['trigramme_form'] = trigramme_form.errors - data_template['errors']['account_form'] = account_form.errors - if all((user_form.is_valid(), cof_form.is_valid(), trigramme_form.is_valid(), account_form.is_valid())): data = {} @@ -123,6 +115,11 @@ def account_create(request): except Account.UserHasAccount as e: messages.error(request, \ "Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme) + else: + messages.error(request, user_form.errors) + messages.error(request, cof_form.errors) + messages.error(request, trigramme_form.errors) + messages.error(request, account_form.errors) return render(request, "kfet/account_create.html", data_template) @@ -227,22 +224,31 @@ def account_update(request, trigramme): and request.user != account.user: raise PermissionDenied + if request.user.has_perm('kfet.is_team'): + user_form = UserRestrictTeamForm(instance=account.user) + group_form = UserGroupForm(instance=account.user) + account_form = AccountForm(instance=account) + cof_form = CofRestrictForm(instance=account.cofprofile) + else: + user_form = UserRestrictForm(instance=account.user) + account_form = None + cof_form = None + group_form = None + if request.method == "POST": # Update attempt + success = False + missing_perm = False - # Peuplement des forms - if request.user.has_perm('kfet.change_account'): - account_form = AccountForm(request.POST, instance = account) - else: - account_form = AccountRestrictForm(request.POST, instance = account) - cof_form = CofRestrictForm(request.POST, instance=account.cofprofile) - user_form = UserRestrictForm(request.POST, instance=account.user) + if request.user.has_perm('kfet.is_team'): + account_form = AccountForm(request.POST, instance=account) + cof_form = CofRestrictForm(request.POST, instance=account.cofprofile) + user_form = UserRestrictTeamForm(request.POST, instance=account.user) + group_form = UserGroupForm(request.POST, instance=account.user) - # Checking permissions - if (request.user.has_perm('kfet.change_account') - or request.user == account.user): - # Permissions ok - if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())): + if (request.user.has_perm('kfet.change_account') + and account_form.is_valid() and cof_form.is_valid() + and user_form.is_valid()): data = {} # Fill data for Account.save() put_cleaned_data_in_dict(data, user_form) @@ -250,37 +256,50 @@ def account_update(request, trigramme): # Updating account_form.save(data = data) - if request.user == account.user: - messages.success(request, - 'Vos informations ont été mises à jour') - else: - messages.success(request, - 'Informations du compte %s mises à jour' % account.trigramme) - #return redirect('kfet.account.read', account.trigramme) - else: - messages.error(request, - 'Informations non mises à jour. Corrigez les erreurs') + + # Checking perm to manage perms + if (request.user.has_perm('kfet.manage_perms') + and group_form.is_valid()): + group_form.save() + + success = True + messages.success(request, + 'Informations du compte %s mises à jour' % account.trigramme) + elif not request.user.has_perm('kfet.change_account'): + missing_perm = True + + if request.user == account.user: + missing_perm = False + user_form = UserRestrictForm(request.POST, instance=account.user) + + if user_form.is_valid(): + user_form.save() + success = True + messages.success(request, 'Vos informations ont été mises à jour') + + if missing_perm: + messages.error('Permission refusée') + if success: + return redirect('kfet.account.read', account.trigramme) else: - # Permissions not ok - if request.user.has_perm('kfet.is_team'): - account_form = AccountForm(request.POST, instance = account) - messages.error(request, 'Permission refusée') - else: - # No update attempt - if request.user.has_perm('kfet.is_team'): - account_form = AccountForm(instance = account) - else: - account_form = AccountRestrictForm(instance = account) - cof_form = CofRestrictForm(instance = account.cofprofile) - user_form = UserRestrictForm(instance = account.user) + messages.error('Informations non mises à jour. Corrigez les erreurs') return render(request, "kfet/account_update.html", { 'account' : account, 'account_form' : account_form, 'cof_form' : cof_form, 'user_form' : user_form, + 'group_form' : group_form, }) +@permission_required('kfet.manage_perms') +def account_group(request): + groups = (Group.objects + .filter(name__icontains='K-Fêt') + .prefetch_related('permissions', 'user_set__profile__account_kfet') + ) + return render(request, 'kfet/account_group.html', { 'groups': groups }) + # ----- # Checkout views # ----- From 8329c0b51159b21db40ccf12826c15fd71154e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 21 Aug 2016 05:51:55 +0200 Subject: [PATCH 076/192] =?UTF-8?q?Ajout=20cr=C3=A9ation/modification=20gr?= =?UTF-8?q?oupes=20K-F=C3=AAt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/forms.py | 12 +- kfet/static/kfet/css/index.css | 9 +- kfet/static/kfet/css/multiple-select.css | 191 +++++ kfet/static/kfet/img/multiple-select.png | Bin 0 -> 3380 bytes kfet/static/kfet/js/multiple-select.js | 782 ++++++++++++++++++++ kfet/templates/kfet/account_group.html | 7 +- kfet/templates/kfet/account_group_form.html | 25 + kfet/urls.py | 6 + kfet/views.py | 14 + 9 files changed, 1043 insertions(+), 3 deletions(-) create mode 100644 kfet/static/kfet/css/multiple-select.css create mode 100644 kfet/static/kfet/img/multiple-select.png create mode 100644 kfet/static/kfet/js/multiple-select.js create mode 100644 kfet/templates/kfet/account_group_form.html diff --git a/kfet/forms.py b/kfet/forms.py index 8dcd00fc..61529b7c 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -1,6 +1,8 @@ from django import forms from django.core.exceptions import ValidationError -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User, Group, Permission +from django.contrib.contenttypes.models import ContentType +from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms import modelformset_factory from kfet.models import (Account, Checkout, Article, OperationGroup, Operation, CheckoutStatement) @@ -92,6 +94,14 @@ class UserGroupForm(forms.ModelForm): model = User fields = ['groups'] +class GroupForm(forms.ModelForm): + permissions = forms.ModelMultipleChoiceField( + queryset= Permission.objects.filter(content_type__in= + ContentType.objects.filter(app_label='kfet'))) + class Meta: + model = Group + fields = ['name', 'permissions'] + # ----- # Checkout forms # ----- diff --git a/kfet/static/kfet/css/index.css b/kfet/static/kfet/css/index.css index fa08b10a..315dad1b 100644 --- a/kfet/static/kfet/css/index.css +++ b/kfet/static/kfet/css/index.css @@ -100,16 +100,23 @@ a:focus, a:hover { .content-right-block { padding-bottom:5px; + position:relative; } .content-right-block:last-child { padding-bottom:15px; } -.content-right-block > div { +.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; } diff --git a/kfet/static/kfet/css/multiple-select.css b/kfet/static/kfet/css/multiple-select.css new file mode 100644 index 00000000..c5b8d183 --- /dev/null +++ b/kfet/static/kfet/css/multiple-select.css @@ -0,0 +1,191 @@ +/** + * @author zhixin wen + */ + +.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; +} diff --git a/kfet/static/kfet/img/multiple-select.png b/kfet/static/kfet/img/multiple-select.png new file mode 100644 index 0000000000000000000000000000000000000000..b1282ce42d52438a36a237dbb6488650272afc65 GIT binary patch literal 3380 zcmV-44a@S0P)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ(iwV_E---f zE+8EQQ5a?h7|H;{3{7l^s6a#!5dlSzpnw6Rp-8NVVj(D~U=K(TP+~BOsHkK{)=GSN zdGF=r_s6~8+Gp=`_t|@&wJrc8PaiHX1(pIJnJ3@}dN|Wpg-6h_{Qw4dfB~ieFj?uT zzCrH6KqN0W7kawL3H*!R3;{^|zGdj?Pp5H0=h0sk8Wyh&7ga7GLtw0fuTQ>mB{3?=`JbBsZ3rr0E=h-EE#ca>7pWA znp#_08k!lIeo?6Zy7)IG?(HJI3i#YJh}QRq?XUb&>HuKOifXg#4_nNB06Mk;Ab0-{ zo8}<^Bt?B|zwyO+XySQ^7YI^qjEyrhGmW?$mXWxizw3WG{0)8aJtOgUzn6#Z%86wP zlLT~e-B>9}DMCIyJ(bDg&<+1Q#Q!+(uk%&0*raG}W_n!s* z`>t?__>spaFD&Aut10z!o?HH?RWufnX30 z)&drY2g!gBGC?lb3<^LI*ah~2N>BspK_h4ZCqM@{4K9Go;5xVo?tlki1dM~{UdPU)xj{ZqAQTQoLvauf5<ZgZNI6o6v>;tbFLDbRL8g&+C=7~%qN5B^ zwkS_j2#SSDLv276qbgBHQSGQ6)GgE~Y6kTQO-3uB4bV1dFZ3#O96A$SfG$Tjpxe-w z(09<|=rSYbRd;g|%>I!rO<0Hzgl9y5R$!^~o_Sb3}g)(-23Wnu-`0_=Y5 zG3+_)Aa)%47DvRX;>>XFxCk5%mxn9IHQ~!?W?(_!4|Qz6*Z? zKaQU#NE37jc7$L;0%0?ug3v;^M0iMeMI;i{iPppbBA2*{SV25ayh0o$z9Y$y^hqwH zNRp7WlXQf1o^+4&icBVJlO4$sWC3|6xsiO4{FwY!f+Arg;U&SA*eFpY(JnD4@j?SR-`K0DzX#{6;CMMSAv!Fl>(L4DIHeoQ<_y) zQT9+yRo<_BQF&U0rsAlQpi-uCR%J?+qH3?oRV`CJr}~U8OLw9t(JSaZ^cgiJHBU96 zTCG~Y+Pu1sdWd?SdaL>)4T1(kBUYnKqg!J}Q&rPfGgq@&^S%~di=h>-wNI;8Yff87 zJ4}0Dt zz%@8vFt8N8)OsmzY2DIcLz1DBVTNI|;iwVK$j2zpsKe-mv8Hi^@owW@<4-0QCP^ms zCJ#(yOjnrZnRc1}YNl_-GOIGXZB90KH{WR9Y5sDV!7|RWgUjw(P%L~cwpnyre6+N( zHrY-t*ICY4 zUcY?IPTh`aS8F$7Pq&Y@KV(1Rpyt4IsB?JYsNu+VY;c@#(sN31I_C7k*~FRe+~z#z zV&k&j<-9B6>fu`G+V3Xg7UEXv_SjwBJ8G6!a$8Ik+VFL5OaMFr+(FGBh%@F?24>HLNsjWR>x%^{cLj zD}-~yJ0q|Wp%D!cv#Z@!?_E6}X%SfvIkZM+P1c&LYZcZetvwSZ8O4k`8I6t(i*Abk z!1QC*F=u1EVya_iST3x6tmkY;b{Tt$W5+4wOvKv7mc~xT*~RUNn~HacFOQ$*x^OGG zFB3cyY7*uW{SuEPE+mB|wI<_|qmxhZWO#|Zo)ndotdxONgVci5ku;mMy=gOiZ+=5M zl)fgtQ$Q8{O!WzMgPUHd;& z##i2{a;|EvR;u1nJ$Hb8VDO;h!Im23nxdNbhq#CC)_T;o*J;<4AI2QcIQ+Cew7&Oi z#@CGv3JpaKACK^kj2sO-+S6#&*x01hRMHGL3!A5oMIO8Pjq5j^Eru<%t+dvnoA$o+&v?IGcZV;atwS+4HIAr!T}^80(JeesFQs#oIjrJ^h!wFI~Cpe)(drQ}4Me zc2`bcwYhrg8sl2Wb<6AReHMLfKUnZUby9Y>+)@{ z+t=@`yfZKqGIV!1a(Lt}`|jkuqXC)@%*Rcr{xo>6OEH*lc%TLr*1x5{cQYs>ht;Of}f>-u708W z;=5lQf9ac9H8cK_|8n8i;#cyoj=Wy>x_j1t_VJtKH}i9aZ{^<}eaCp$`#$Xb#C+xl z?1zevdLO$!d4GDiki4+)8~23s`{L#u!TGiX9xf`7!90J%gqqrG=1>+6o;yD7thK`X5wW{0~HI=~lWGL`sK( z6!i5umvh6(y-!I3cQ_CHNZwzx{Dm_7^C#duUm7bu|6I6PFN>5Pf2`W87r6;KL1eK+* z!5z$1a9zOf`?%^XSw|{L5`(4QfL|cqZhZ$!`f^{gj#QLHf<->+Qb*tw*w$m=ad?wS z){%;mV1Y$l + * @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( + '
    ', + $el.attr('class') || '', + sprintf('title="%s"', $el.attr('title')))); + + // add placeholder to choice button + this.$choice = $(sprintf([ + '' + ].join(''), + this.options.placeholder)); + + // default position is bottom + this.$drop = $(sprintf('
    ', + 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 = $('
      '); + + this.$drop.html(''); + + if (this.options.filter) { + this.$drop.append([ + ''].join('') + ); + } + + if (this.options.selectAll && !this.options.single) { + $ul.append([ + '
    • ', + '', + '
    • ' + ].join('')); + } + + $.each(this.$el.children(), function (i, elm) { + $ul.append(that.optionToHtml(i, elm)); + }); + $ul.append(sprintf('
    • %s
    • ', 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('
    • ', multiple, classes, title, style), + sprintf('', + '
    • ' + ].join('')); + $el.find('input').val(value); + return $el; + } + if ($elm.is('optgroup')) { + var label = that.options.labelTemplate($elm), + $group = $('
      '); + + group = 'group_' + i; + disabled = $elm.prop('disabled'); + + $group.append([ + '
    • ', + sprintf('', + '
    • ' + ].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 + + + + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index df597766..82b05af4 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -42,6 +42,12 @@ urlpatterns = [ # Account - Groups url(r'^accounts/groups$', views.account_group, name = 'kfet.account.group'), + url(r'^accounts/groups/new$', + permission_required('kfet.manage_perms')(views.AccountGroupCreate.as_view()), + name = 'kfet.account.group.create'), + url(r'^accounts/groups/(?P\d+)/edit$', + permission_required('kfet.manage_perms')(views.AccountGroupUpdate.as_view()), + name = 'kfet.account.group.update'), # ----- # Checkout urls diff --git a/kfet/views.py b/kfet/views.py index 60dc3798..738788d5 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -300,6 +300,20 @@ def account_group(request): ) return render(request, 'kfet/account_group.html', { 'groups': groups }) +class AccountGroupCreate(SuccessMessageMixin, CreateView): + model = Group + template_name = 'kfet/account_group_form.html' + form_class = GroupForm + success_message = 'Nouveau groupe : %(name)s' + success_url = reverse_lazy('kfet.account.group') + +class AccountGroupUpdate(UpdateView): + queryset = Group.objects.filter(name__icontains='K-Fêt') + template_name = 'kfet/account_group_form.html' + form_class = GroupForm + success_message = 'Groupe modifié : %(name)s' + success_url = reverse_lazy('kfet.account.group') + # ----- # Checkout views # ----- From 1ad265ef0fc866a14296569e7fa8cee2323d28da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 21 Aug 2016 16:30:22 +0200 Subject: [PATCH 077/192] Fix 404 widget media MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avec ça, ça marche. Mais si qqn trouve mieux, et bien c'est bien --- cof/settings_dev.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cof/settings_dev.py b/cof/settings_dev.py index 86513843..5f9741e4 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -123,6 +123,10 @@ USE_TZ = True STATIC_URL = '/static/' +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'static/'), +) + # Media upload (through ImageField, SiteField) # https://docs.djangoproject.com/en/1.9/ref/models/fields/ From a6b961d2ab2bad5294ac60ba10795992c6bb7361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 21 Aug 2016 16:45:25 +0200 Subject: [PATCH 078/192] =?UTF-8?q?Texte=20dernier=20relev=C3=A9=20ne=20s'?= =?UTF-8?q?affiche=20pas=20s'il=20n'y=20a=20en=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/kpsul.html | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index c4fddb27..861390ae 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -49,11 +49,7 @@
      En caisse:
      -
      - Dernier relevé:
      - € - à - par +
      @@ -259,9 +255,17 @@ $(document).ready(function() { 'last_statement_by_first_name': '', 'last_statement_by_last_name' : '' , } + var last_statement_container = $('#last_statement'); + var last_statement_html_default = 'Dernier relevé:
      € à par '; + // Display data function displayCheckoutData() { + if (checkout_data['last_statement_at']) { + last_statement_container.html(last_statement_html_default); + } else { + last_statement_container.html(''); + } for (var elem in checkout_data) { $('#checkout-'+elem).text(checkout_data[elem]); } From 74c3f07c666856bd1c45c8b886d2d77c4d5927c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 21 Aug 2016 18:10:35 +0200 Subject: [PATCH 079/192] =?UTF-8?q?Ajout=20nouvelle=20cat=C3=A9gorie/artic?= =?UTF-8?q?le=20+=20auth/article?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lors de la création ou modification d'un article, il est possible de sélectionner une catégorie existante ou d'en créer une nouvelle. - Autorisations pour la création/modif d'article prises en compte correctement. Si l'utilisateur identifié n'a pas la permission add(ou change)_accoount, input password apparait et l'utilisateur est identifié temporairement pour la validation de l'ajout/modif. --- kfet/forms.py | 20 +++++++++++++++++++- kfet/templates/kfet/article_create.html | 3 +++ kfet/templates/kfet/article_update.html | 3 +++ kfet/views.py | 11 +++++++---- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/kfet/forms.py b/kfet/forms.py index 61529b7c..17add53b 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms import modelformset_factory from kfet.models import (Account, Checkout, Article, OperationGroup, Operation, - CheckoutStatement) + CheckoutStatement, ArticleCategory) from gestioncof.models import CofProfile # ----- @@ -129,6 +129,24 @@ class CheckoutStatementForm(forms.ModelForm): # ----- class ArticleForm(forms.ModelForm): + category_new = forms.CharField( + max_length=45, + required = False) + category = forms.ModelChoiceField( + queryset = ArticleCategory.objects.all(), + required = False) + + 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'] diff --git a/kfet/templates/kfet/article_create.html b/kfet/templates/kfet/article_create.html index 6ab1c2b2..742756b2 100644 --- a/kfet/templates/kfet/article_create.html +++ b/kfet/templates/kfet/article_create.html @@ -8,6 +8,9 @@
      {% csrf_token %} {{ form.as_p }} + {% if not perms.kfet.add_article %} + + {% endif %}
      diff --git a/kfet/templates/kfet/article_update.html b/kfet/templates/kfet/article_update.html index 66e97f78..db0107cc 100644 --- a/kfet/templates/kfet/article_update.html +++ b/kfet/templates/kfet/article_update.html @@ -8,6 +8,9 @@
      {% csrf_token %} {{ form.as_p }} + {% if not perms.kfet.change_article %} + + {% endif %}
      diff --git a/kfet/views.py b/kfet/views.py index 738788d5..fac18cc5 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -432,8 +432,10 @@ class ArticleCreate(SuccessMessageMixin, CreateView): # Surcharge de la validation def form_valid(self, form): # Checking permission - if not self.request.user.has_perm('add_article'): - raise PermissionDenied + if not self.request.user.has_perm('kfet.add_article'): + form.add_error(None, 'Permission refusée') + return self.form_invalid(form) + # Creating return super(ArticleCreate, self).form_valid(form) @@ -455,8 +457,9 @@ class ArticleUpdate(UpdateView): # Surcharge de la validation def form_valid(self, form): # Checking permission - if not self.request.user.has_perm('change_article'): - raise PermissionDenied + if not self.request.user.has_perm('kfet.change_article'): + form.add_error(None, 'Permission refusée') + return self.form_invalid(form) # Updating return super(ArticleUpdate, self).form_valid(form) From 90e8ece78340624b4edc547e355994cf90d5b712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 22 Aug 2016 01:57:28 +0200 Subject: [PATCH 080/192] =?UTF-8?q?Affichage=20et=20auth=20sur=20les=20cai?= =?UTF-8?q?sses=20et=20relev=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auth K-Fêt fonctionne - Affichage repris --- kfet/templates/kfet/checkout.html | 34 +++++++++---- kfet/templates/kfet/checkout_create.html | 3 ++ kfet/templates/kfet/checkout_read.html | 48 +++++++++---------- kfet/templates/kfet/checkout_update.html | 41 +++++++++++----- .../kfet/checkoutstatement_create.html | 29 ++++++++--- kfet/templates/kfet/left_checkout.html | 13 +++++ kfet/views.py | 3 +- 7 files changed, 117 insertions(+), 54 deletions(-) create mode 100644 kfet/templates/kfet/left_checkout.html diff --git a/kfet/templates/kfet/checkout.html b/kfet/templates/kfet/checkout.html index 2e2cea17..43d19950 100644 --- a/kfet/templates/kfet/checkout.html +++ b/kfet/templates/kfet/checkout.html @@ -1,22 +1,36 @@ {% extends "kfet/base.html" %} -{% block title %}Caisses{% endblock %} +{% block title %}Liste des caisses{% endblock %} {% block content-header-title %}Caisses{% endblock %} {% block content %}
      -
      -
      - Créer une caisse +
      +
      +
      +
      {{ checkouts|length }} caisses
      +
      + +
      +
      +
      + {% include 'kfet/base_messages.html' %} +
      +
      +

      Liste des caisses

      +
      + +
      +
      - - {% endblock %} diff --git a/kfet/templates/kfet/checkout_create.html b/kfet/templates/kfet/checkout_create.html index 53e5ef8c..0e283825 100644 --- a/kfet/templates/kfet/checkout_create.html +++ b/kfet/templates/kfet/checkout_create.html @@ -16,6 +16,9 @@

      {{ field.help_text|safe }}

      {% endif %} {% endfor %} + {% if not perms.kfet.add_checkout %} + + {% endif %} diff --git a/kfet/templates/kfet/checkout_read.html b/kfet/templates/kfet/checkout_read.html index 2e9fa932..77804af7 100644 --- a/kfet/templates/kfet/checkout_read.html +++ b/kfet/templates/kfet/checkout_read.html @@ -6,32 +6,32 @@ {% block content %}
      -
      -
      - Modifier - Effectuer un relevé +
      +
      + {% include 'kfet/left_checkout.html' %} +
      +
      +
      + {% include "kfet/base_messages.html" %} +
      +
      +

      Relevés

      +
      + {% with statements=checkout.statements.all %} + {% if not statements %} + Pas de relevé + {% else %} +
        + {% for statement in statements %} +
      • {{ statement }}
      • + {% endfor %} +
      + {% endif %} + {% endwith %} +
      +
      -

      Nom: {{ checkout.name }}

      -

      Valide du {{ checkout.valid_from|date:'l j F Y, G:i' }} au {{ checkout.valid_to|date:'l j F Y, G:i' }}

      -

      Créée par: {{ checkout.created_by }}

      -

      Balance: {{ checkout.balance }} €

      -

      Protected: {{ checkout.is_protected }}

      - -Relevés: -{% with statements=checkout.statements.all %} -{% if not statements %} - Pas de relevé -{% else %} -
        - {% for statement in statements %} -
      • {{ statement }}
      • - {% endfor %} -
      -{% endif %} -{% endwith %} - - {% endblock %} diff --git a/kfet/templates/kfet/checkout_update.html b/kfet/templates/kfet/checkout_update.html index 121176aa..2b3d9183 100644 --- a/kfet/templates/kfet/checkout_update.html +++ b/kfet/templates/kfet/checkout_update.html @@ -6,18 +6,35 @@ {% block content %} -
      - {% csrf_token %} - {% for field in form %} - {{ field.errors }} - {{ field.label_tag }} -
      {{ field }}
      - {% if field.help_text %} -

      {{ field.help_text|safe }}

      - {% endif %} - {% endfor %} - -
      +
      +
      +
      + {% include 'kfet/left_checkout.html' %} +
      +
      +
      + {% include "kfet/base_messages.html" %} +
      +
      +
      + {% csrf_token %} + {% for field in form %} + {{ field.errors }} + {{ field.label_tag }} +
      {{ field }}
      + {% if field.help_text %} +

      {{ field.help_text|safe }}

      + {% endif %} + {% endfor %} + {% if not perms.kfet.add_checkout %} + + {% endif %} + +
      +
      +
      +
      +
      + {% endblock %} diff --git a/kfet/views.py b/kfet/views.py index ac8afcb8..87784e6c 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -230,11 +230,16 @@ def account_update(request, trigramme): group_form = UserGroupForm(instance=account.user) account_form = AccountForm(instance=account) cof_form = CofRestrictForm(instance=account.cofprofile) + if hasattr(account, 'negative'): + negative_form = AccountNegativeForm(instance=account.negative) + else: + negative_form = None else: user_form = UserRestrictForm(instance=account.user) account_form = None cof_form = None group_form = None + negative_form = None if request.method == "POST": # Update attempt @@ -246,6 +251,8 @@ def account_update(request, trigramme): cof_form = CofRestrictForm(request.POST, instance=account.cofprofile) user_form = UserRestrictTeamForm(request.POST, instance=account.user) group_form = UserGroupForm(request.POST, instance=account.user) + if hasattr(account, 'negative'): + negative_form = AccountNegativeForm(request.POST, instance=account.negative) if (request.user.has_perm('kfet.change_account') and account_form.is_valid() and cof_form.is_valid() @@ -254,6 +261,7 @@ def account_update(request, trigramme): # Fill data for Account.save() put_cleaned_data_in_dict(data, user_form) put_cleaned_data_in_dict(data, cof_form) + print(vars(account.negative)) # Updating account_form.save(data = data) @@ -262,6 +270,25 @@ def account_update(request, trigramme): if (request.user.has_perm('kfet.manage_perms') and group_form.is_valid()): group_form.save() + print(vars(account.negative)) + + # Checking perm to manage negative + if hasattr(account, 'negative'): + balance_offset_old = 0 + if account.negative.balance_offset: + balance_offset_old = account.negative.balance_offset + if (hasattr(account, 'negative') + and request.user.has_perm('kfet.change_accountnegative') + and negative_form.is_valid()): + balance_offset_new = negative_form.cleaned_data['balance_offset'] + if not balance_offset_new: + balance_offset_new = 0 + balance_offset_diff = balance_offset_old - balance_offset_new + Account.objects.filter(pk=account.pk).update( + balance = F('balance') + balance_offset_diff) + negative_form.save() + if not balance_offset_new and Account.objects.get(pk=account.pk).balance >= 0: + AccountNegative.objects.get(account=account).delete() success = True messages.success(request, @@ -291,6 +318,7 @@ def account_update(request, trigramme): 'cof_form' : cof_form, 'user_form' : user_form, 'group_form' : group_form, + 'negative_form': negative_form, }) @permission_required('kfet.manage_perms') From f73b25e65ffb113a355aa0d5e3ab3a30ac7adb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 00:15:17 +0200 Subject: [PATCH 094/192] =?UTF-8?q?Am=C3=A9lioration=20gestion=20des=20rel?= =?UTF-8?q?ev=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau relevé: Il faut donner le détail du nombre de chaque pièces/billets pris et laissé en caisse pour calculer les valeurs `balance_new` et `amount_taken` d'un relevé (`CheckoutStatement`). L'erreur est directement calculée par rapport à la balance actuelle de la caisse et ces 2 valeurs. Une erreur positive correspond à un surplus d'argent et inversement. Modification d'un relevé: Il est possible de modifier les infos d'un ancien relevé. L'erreur est ensuite recalculée à partir de ces infos. Important: Dans le cas où `balance_new` est modifiée et qu'il s'agit du relevé le plus récent sur cette caisse. Alors la balance de la caisse est mise à jour en prenant en compte cette correction (et en conservant les modifications s'il y a eu des mouvements sur la caisse) --- kfet/forms.py | 27 +++++- kfet/migrations/0032_auto_20160822_2350.py | 94 +++++++++++++++++++ kfet/models.py | 30 ++++++ .../kfet/checkoutstatement_create.html | 2 +- .../kfet/checkoutstatement_update.html | 33 +++++++ kfet/urls.py | 4 + kfet/views.py | 48 +++++++++- 7 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 kfet/migrations/0032_auto_20160822_2350.py create mode 100644 kfet/templates/kfet/checkoutstatement_update.html diff --git a/kfet/forms.py b/kfet/forms.py index 9d164746..26ada7bc 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -130,10 +130,33 @@ class CheckoutRestrictForm(CheckoutForm): class Meta(CheckoutForm.Meta): fields = ['name', 'valid_from', 'valid_to'] -class CheckoutStatementForm(forms.ModelForm): + +class CheckoutStatementCreateForm(forms.ModelForm): + balance_001 = forms.IntegerField(min_value=0, initial=0) + balance_002 = forms.IntegerField(min_value=0, initial=0) + balance_005 = forms.IntegerField(min_value=0, initial=0) + balance_01 = forms.IntegerField(min_value=0, initial=0) + balance_02 = forms.IntegerField(min_value=0, initial=0) + balance_05 = forms.IntegerField(min_value=0, initial=0) + balance_1 = forms.IntegerField(min_value=0, initial=0) + balance_2 = forms.IntegerField(min_value=0, initial=0) + balance_5 = forms.IntegerField(min_value=0, initial=0) + balance_10 = forms.IntegerField(min_value=0, initial=0) + balance_20 = forms.IntegerField(min_value=0, initial=0) + balance_50 = forms.IntegerField(min_value=0, initial=0) + balance_100 = forms.IntegerField(min_value=0, initial=0) + balance_200 = forms.IntegerField(min_value=0, initial=0) + balance_500 = forms.IntegerField(min_value=0, initial=0) + class Meta: model = CheckoutStatement - fields = ['balance_new', 'amount_taken'] + exclude = ['by', 'at', 'checkout', 'amount_error', 'amount_taken', + 'balance_old', 'balance_new'] + +class CheckoutStatementUpdateForm(forms.ModelForm): + class Meta: + model = CheckoutStatement + exclude = ['by', 'at', 'checkout', 'amount_error', 'amount_taken'] # ----- # Article forms diff --git a/kfet/migrations/0032_auto_20160822_2350.py b/kfet/migrations/0032_auto_20160822_2350.py new file mode 100644 index 00000000..142fb29d --- /dev/null +++ b/kfet/migrations/0032_auto_20160822_2350.py @@ -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), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 1cdcb409..39953196 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -7,6 +7,7 @@ from gestioncof.models import CofProfile from django.utils.six.moves import reduce from django.utils import timezone from django.db import transaction +from django.db.models import F from django.core.cache import cache from datetime import date, timedelta import re @@ -259,6 +260,23 @@ class CheckoutStatement(models.Model): amount_error = models.DecimalField(max_digits = 6, decimal_places = 2) at = models.DateTimeField(auto_now_add = True) + 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.PositiveSmallIntegerField(default=0) + def __str__(self): return '%s %s' % (self.checkout, self.at) @@ -273,6 +291,18 @@ class CheckoutStatement(models.Model): 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) class ArticleCategory(models.Model): diff --git a/kfet/templates/kfet/checkoutstatement_create.html b/kfet/templates/kfet/checkoutstatement_create.html index 8e59bcb2..d9400f9b 100644 --- a/kfet/templates/kfet/checkoutstatement_create.html +++ b/kfet/templates/kfet/checkoutstatement_create.html @@ -18,7 +18,7 @@
      {% csrf_token %} {{ form.as_p }} - {% if not perms.ket.add_checkoutstatement %} + {% if not perms.kfet.add_checkoutstatement %} {% endif %} diff --git a/kfet/templates/kfet/checkoutstatement_update.html b/kfet/templates/kfet/checkoutstatement_update.html new file mode 100644 index 00000000..18d8938f --- /dev/null +++ b/kfet/templates/kfet/checkoutstatement_update.html @@ -0,0 +1,33 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Modification d'un relevé{% endblock %} +{% block content-header-title %} +Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }} +{% endblock %} + +{% block content %} + +
      +
      +
      + {% include 'kfet/left_checkout.html' %} +
      +
      +
      + {% include 'kfet/base_messages.html' %} +
      +
      + + {% csrf_token %} + {{ form.as_p }} + {% if not perms.kfet.change_checkoutstatement %} + + {% endif %} + + +
      +
      +
      +
      + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 1ce9b7a7..7b154c35 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -80,6 +80,10 @@ urlpatterns = [ url('^checkouts/(?P\d+)/statements/add', permission_required('kfet.is_team')(views.CheckoutStatementCreate.as_view()), name = 'kfet.checkoutstatement.create'), + # Checkout Statement - Update + url('^checkouts/(?P\d+)/statements/(?P\d+)/edit', + permission_required('kfet.is_team')(views.CheckoutStatementUpdate.as_view()), + name = 'kfet.checkoutstatement.update'), # ----- # Article urls diff --git a/kfet/views.py b/kfet/views.py index 87784e6c..418347c0 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -410,10 +410,30 @@ class CheckoutStatementList(ListView): # Checkout Statement - Create +def getAmountTaken(data): + return Decimal(data.taken_001 * 0.01 + data.taken_002 * 0.02 + + data.taken_005 * 0.05 + data.taken_01 * 0.1 + + data.taken_02 * 0.2 + data.taken_05 * 0.5 + + data.taken_1 * 1 + data.taken_2 * 2 + + data.taken_5 * 5 + data.taken_10 * 10 + + data.taken_20 * 20 + data.taken_50 * 50 + + data.taken_100 * 100 + data.taken_200 * 200 + + data.taken_500 * 500 + data.taken_cheque) + +def getAmountBalance(data): + return Decimal(data['balance_001'] * 0.01 + data['balance_002'] * 0.02 + + data['balance_005'] * 0.05 + data['balance_01'] * 0.1 + + data['balance_02'] * 0.2 + data['balance_05'] * 0.5 + + data['balance_1'] * 1 + data['balance_2'] * 2 + + data['balance_5'] * 5 + data['balance_10'] * 10 + + data['balance_20'] * 20 + data['balance_50'] * 50 + + data['balance_100'] * 100 + data['balance_200'] * 200 + + data['balance_500'] * 500) + class CheckoutStatementCreate(SuccessMessageMixin, CreateView): model = CheckoutStatement template_name = 'kfet/checkoutstatement_create.html' - form_class = CheckoutStatementForm + form_class = CheckoutStatementCreateForm success_message = 'Nouveau relevé : %(checkout)s - %(at)s' def get_success_url(self): @@ -437,10 +457,36 @@ class CheckoutStatementCreate(SuccessMessageMixin, CreateView): form.add_error(None, 'Permission refusée') return self.form_invalid(form) # Creating + form.instance.amount_taken = getAmountTaken(form.instance) + form.instance.balance_new = getAmountBalance(form.cleaned_data) form.instance.checkout_id = self.kwargs['pk_checkout'] form.instance.by = self.request.user.profile.account_kfet return super(CheckoutStatementCreate, self).form_valid(form) +class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView): + model = CheckoutStatement + template_name = 'kfet/checkoutstatement_update.html' + form_class = CheckoutStatementUpdateForm + success_message = 'Relevé modifié' + + def get_success_url(self): + return reverse_lazy('kfet.checkout.read', kwargs={'pk':self.kwargs['pk_checkout']}) + + def get_context_data(self, **kwargs): + context = super(CheckoutStatementUpdate, self).get_context_data(**kwargs) + checkout = Checkout.objects.get(pk=self.kwargs['pk_checkout']) + context['checkout'] = checkout + return context + + def form_valid(self, form): + # Checking permission + if not self.request.user.has_perm('kfet.change_checkoutstatement'): + form.add_error(None, 'Permission refusée') + return self.form_invalid(form) + # Updating + form.instance.amount_taken = getAmountTaken(form.instance) + return super(CheckoutStatementUpdate, self).form_valid(form) + # ----- # Article views # ----- From b6c75fd84aa58d8a38a046b6058b3f2028ae6d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 02:45:49 +0200 Subject: [PATCH 095/192] =?UTF-8?q?Assistant=20sur=20nouveau=20relev=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reprise de l'affichage - Affichage en direct des totaux et erreurs - Possibilité de ne pas compter la caisse ajoutée (et identifiée par `not_count` dans le modèle Statement si tel est le cas) --- kfet/forms.py | 51 ++++-- .../0033_checkoutstatement_not_count.py | 19 ++ kfet/migrations/0034_auto_20160823_0206.py | 19 ++ kfet/models.py | 7 +- .../kfet/checkoutstatement_create.html | 167 ++++++++++++++++-- kfet/views.py | 5 +- 6 files changed, 239 insertions(+), 29 deletions(-) create mode 100644 kfet/migrations/0033_checkoutstatement_not_count.py create mode 100644 kfet/migrations/0034_auto_20160823_0206.py diff --git a/kfet/forms.py b/kfet/forms.py index 26ada7bc..71f5a78e 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -132,27 +132,48 @@ class CheckoutRestrictForm(CheckoutForm): class CheckoutStatementCreateForm(forms.ModelForm): - balance_001 = forms.IntegerField(min_value=0, initial=0) - balance_002 = forms.IntegerField(min_value=0, initial=0) - balance_005 = forms.IntegerField(min_value=0, initial=0) - balance_01 = forms.IntegerField(min_value=0, initial=0) - balance_02 = forms.IntegerField(min_value=0, initial=0) - balance_05 = forms.IntegerField(min_value=0, initial=0) - balance_1 = forms.IntegerField(min_value=0, initial=0) - balance_2 = forms.IntegerField(min_value=0, initial=0) - balance_5 = forms.IntegerField(min_value=0, initial=0) - balance_10 = forms.IntegerField(min_value=0, initial=0) - balance_20 = forms.IntegerField(min_value=0, initial=0) - balance_50 = forms.IntegerField(min_value=0, initial=0) - balance_100 = forms.IntegerField(min_value=0, initial=0) - balance_200 = forms.IntegerField(min_value=0, initial=0) - balance_500 = forms.IntegerField(min_value=0, initial=0) + 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 diff --git a/kfet/migrations/0033_checkoutstatement_not_count.py b/kfet/migrations/0033_checkoutstatement_not_count.py new file mode 100644 index 00000000..50c58256 --- /dev/null +++ b/kfet/migrations/0033_checkoutstatement_not_count.py @@ -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), + ), + ] diff --git a/kfet/migrations/0034_auto_20160823_0206.py b/kfet/migrations/0034_auto_20160823_0206.py new file mode 100644 index 00000000..90d0965c --- /dev/null +++ b/kfet/migrations/0034_auto_20160823_0206.py @@ -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), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 39953196..52862f1b 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -259,6 +259,7 @@ class CheckoutStatement(models.Model): 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) @@ -275,7 +276,7 @@ class CheckoutStatement(models.Model): taken_100 = models.PositiveSmallIntegerField(default=0) taken_200 = models.PositiveSmallIntegerField(default=0) taken_500 = models.PositiveSmallIntegerField(default=0) - taken_cheque = 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) @@ -285,8 +286,10 @@ class CheckoutStatement(models.Model): 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) + 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) diff --git a/kfet/templates/kfet/checkoutstatement_create.html b/kfet/templates/kfet/checkoutstatement_create.html index d9400f9b..0edb66ad 100644 --- a/kfet/templates/kfet/checkoutstatement_create.html +++ b/kfet/templates/kfet/checkoutstatement_create.html @@ -1,4 +1,5 @@ {% extends "kfet/base.html" %} +{% load l10n %} {% block title %}Nouveau relevé{% endblock %} {% block content-header-title %}Caisse {{ checkout.name }} - Nouveau relevé{% endblock %} @@ -14,18 +15,164 @@
      {% include "kfet/base_messages.html" %}
      -
      -
      - {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.add_checkoutstatement %} - - {% endif %} - -
      -
      +
      + {% csrf_token %} +
      +

      Général

      +
      +
      + Ancienne balance : {{ checkout.balance|unlocalize }}
      + Nouvelle balance : 0
      + Pris : 0
      + Erreur : 0
      + {% if not perms.kfet.add_checkoutstatement %} + + + {% endif %} + +
      +
      +
      +

      Pris

      +
      + + + + + + + + + + + + + + + + + + + +
      5€10€20€50€100€200€500€
      + + + + + + + + + + + + + + + + + + + + + +
      2€1€0.50€0.20€0.10€0.05€0.02€0.01€
      + Chèque: +
      +
      +
      +

      En caisse

      +
      + + + + + + + + + + + + + + + + + + + +
      5€10€20€50€100€200€500€
      + + + + + + + + + + + + + + + + + + + + + +
      2€1€0.50€0.20€0.10€0.05€0.02€0.01€
      +
      +
      +
      + + {% endblock %} diff --git a/kfet/views.py b/kfet/views.py index 418347c0..ab70c640 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -418,7 +418,7 @@ def getAmountTaken(data): + data.taken_5 * 5 + data.taken_10 * 10 + data.taken_20 * 20 + data.taken_50 * 50 + data.taken_100 * 100 + data.taken_200 * 200 - + data.taken_500 * 500 + data.taken_cheque) + + data.taken_500 * 500 + float(data.taken_cheque)) def getAmountBalance(data): return Decimal(data['balance_001'] * 0.01 + data['balance_002'] * 0.02 @@ -458,7 +458,8 @@ class CheckoutStatementCreate(SuccessMessageMixin, CreateView): return self.form_invalid(form) # Creating form.instance.amount_taken = getAmountTaken(form.instance) - form.instance.balance_new = getAmountBalance(form.cleaned_data) + if not form.instance.not_count: + form.instance.balance_new = getAmountBalance(form.cleaned_data) form.instance.checkout_id = self.kwargs['pk_checkout'] form.instance.by = self.request.user.profile.account_kfet return super(CheckoutStatementCreate, self).form_valid(form) From 6a175eee08f3137658843e4661f6d0449d1be5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 03:00:09 +0200 Subject: [PATCH 096/192] =?UTF-8?q?Relev=C3=A9s=20d'une=20caisse=20tri?= =?UTF-8?q?=C3=A9=C3=A9s=20par=20date=20desc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/checkout_read.html | 20 +++++++++----------- kfet/views.py | 5 +++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/kfet/templates/kfet/checkout_read.html b/kfet/templates/kfet/checkout_read.html index 77804af7..f0348e3c 100644 --- a/kfet/templates/kfet/checkout_read.html +++ b/kfet/templates/kfet/checkout_read.html @@ -17,17 +17,15 @@

      Relevés

      - {% with statements=checkout.statements.all %} - {% if not statements %} - Pas de relevé - {% else %} -
        - {% for statement in statements %} -
      • {{ statement }}
      • - {% endfor %} -
      - {% endif %} - {% endwith %} + {% if not statements %} + Pas de relevé + {% else %} + + {% endif %}
      diff --git a/kfet/views.py b/kfet/views.py index ab70c640..c1b39550 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -379,6 +379,11 @@ class CheckoutRead(DetailView): template_name = 'kfet/checkout_read.html' context_object_name = 'checkout' + def get_context_data(self, **kwargs): + context = super(CheckoutRead, self).get_context_data(**kwargs) + context['statements'] = context['checkout'].statements.order_by('-at') + return context + # Checkout - Update class CheckoutUpdate(SuccessMessageMixin, UpdateView): From 313764dcf4bfe854ba08404431ec28d264f2723d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 03:07:47 +0200 Subject: [PATCH 097/192] =?UTF-8?q?Plus=20d'infos=20dans=20la=20liste=20de?= =?UTF-8?q?s=20relev=C3=A9s=20d'une=20caisse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/checkout_read.html | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kfet/templates/kfet/checkout_read.html b/kfet/templates/kfet/checkout_read.html index f0348e3c..04134c03 100644 --- a/kfet/templates/kfet/checkout_read.html +++ b/kfet/templates/kfet/checkout_read.html @@ -20,11 +20,27 @@ {% if not statements %} Pas de relevé {% else %} - + + + + + + + + + + + {% for statement in statements %} + + + + + + + + {% endfor %} + +
      Date/heureMontant prisMontant laisséErreur
      {{ statement.at }}{{ statement.amount_taken }}{{ statement.balance_new }}{{ statement.amount_error }}
      {% endif %}
      From 093c7ffb0eec952b111baf21613f5d8efec27184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 03:27:02 +0200 Subject: [PATCH 098/192] Annulation et balance de caisses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Une opération annulée alors qu'il y a eu un relevé depuis ne modifie plus la balance de la caisse - Fix maj balance caisse lors d'une annulation sur LIQ --- kfet/views.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/kfet/views.py b/kfet/views.py index c1b39550..2b7ed195 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -872,12 +872,22 @@ def kpsul_cancel_operations(request): to_accounts_balances[ope.addcost_for] -= ope.addcost_amount # Pour les groupes d'opés to_groups_amounts[ope.group] -= ope.amount + # Pour les balances de caisses - if ope.type == Operation.PURCHASE: - if ope.group.on_acc.is_cash: - to_checkouts_balances[ope.group.on_acc] -= - ope.amount - else: - to_checkouts_balances[ope.group.checkout] -= ope.amount + # Les balances de caisses dont il y a eu un relevé depuis la date + # de la commande ne doivent pas être modifiées + # TODO ? : Maj les balance_old de relevés pour modifier l'erreur + last_statement = (CheckoutStatement.objects + .filter(checkout=ope.group.checkout) + .order_by('at') + .last()) + if not last_statement or last_statement.at < ope.group.at: + if ope.type == Operation.PURCHASE: + if ope.group.on_acc.is_cash: + to_checkouts_balances[ope.group.checkout] -= - ope.amount + else: + to_checkouts_balances[ope.group.checkout] -= ope.amount + # Pour les stocks d'articles if ope.article and ope.article_nb: to_articles_stocks[ope.article] += ope.article_nb From 8f76986f89798e49ca6e7520e49524ec0c4544e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 03:39:33 +0200 Subject: [PATCH 099/192] =?UTF-8?q?Affichage=20date=20dernier=20relev?= =?UTF-8?q?=C3=A9=20K-Psul=20en=20heure=20locale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/kpsul.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 342f3fa7..51a178d0 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -272,19 +272,29 @@ $(document).ready(function() { 'last_statement_by_last_name' : '' , } var last_statement_container = $('#last_statement'); - var last_statement_html_default = 'Dernier relevé:
      € à par '; + var last_statement_html_default = 'Dernier relevé:
      € le par '; // Display data function displayCheckoutData() { + var at_formated = ''; if (checkout_data['last_statement_at']) { last_statement_container.html(last_statement_html_default); + at = new Date(checkout_data['last_statement_at']); + var at_date = at.getDate(); + var at_month = at.getMonth()+1; + var at_year = at.getFullYear(); + var at_hours = at.getHours(); + var at_minutes = (at.getMinutes() < 10 ? '0' : '') + at.getMinutes(); + var at_seconds = (at.getSeconds() < 10 ? '0' : '') + at.getSeconds(); + at_formated = at_date+'/'+at_month+'/'+at_year+' à '+at_hours+':'+at_minutes; } else { last_statement_container.html(''); } for (var elem in checkout_data) { $('#checkout-'+elem).text(checkout_data[elem]); } + $('#checkout-last_statement_at').text(at_formated); var buttons = ''; if (checkout_data['id'] !== 0) { buttons += ''; From f7e9cceb70170ddff2ea3b50badd245a7fb88fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 04:35:09 +0200 Subject: [PATCH 100/192] Utilisation de moment.js sur K-Psul MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parce que bon, ça fait un peu plus sérieux --- kfet/templates/kfet/kpsul.html | 31 ++++++++++++------------------- kfet/views.py | 2 +- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 51a178d0..4e85eaf0 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -8,6 +8,9 @@ + + + {% endblock %} {% block title %}K-Psul{% endblock %} @@ -280,14 +283,8 @@ $(document).ready(function() { var at_formated = ''; if (checkout_data['last_statement_at']) { last_statement_container.html(last_statement_html_default); - at = new Date(checkout_data['last_statement_at']); - var at_date = at.getDate(); - var at_month = at.getMonth()+1; - var at_year = at.getFullYear(); - var at_hours = at.getHours(); - var at_minutes = (at.getMinutes() < 10 ? '0' : '') + at.getMinutes(); - var at_seconds = (at.getSeconds() < 10 ? '0' : '') + at.getSeconds(); - at_formated = at_date+'/'+at_month+'/'+at_year+' à '+at_hours+':'+at_minutes; + var at = moment.tz(checkout_data['last_statement_at'], 'UTC'); + at_formated = at.tz('Europe/Paris').format('DD/MM/YY à HH:mm'); } else { last_statement_container.html(''); } @@ -1003,11 +1000,8 @@ $(document).ready(function() { function getOpegroupHtml(opegroup) { var opegroup_html = $(history_operationgroup_html); - var at = new Date(opegroup['at']); - var at_hours = (at.getHours() < 10 ? '0' : '') + at.getHours(); - var at_minutes = (at.getMinutes() < 10 ? '0' : '') + at.getMinutes(); - var at_seconds = (at.getSeconds() < 10 ? '0' : '') + at.getSeconds(); - var at_formated = at_hours+':'+at_minutes+':'+at_seconds; + var at = moment.tz(opegroup['at'], 'UTC'); + var at_formated = at.tz('Europe/Paris').format('HH:mm:ss'); var amount = parseFloat(opegroup['amount']); var trigramme = opegroup['on_acc__trigramme']; if (opegroup['on_acc__trigramme'] == 'LIQ') { @@ -1044,17 +1038,16 @@ $(document).ready(function() { } var history_day_default_html = '
      '; - var months = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']; function checkOrCreateDay(at) { - var at = new Date(at); + var at = moment.tz(at, 'UTC').tz('Europe/Paris'); var mostRecentDay = history_container.find('.day').first(); - if (mostRecentDay.length == 0 || at.getMonth() != mostRecentDay.attr('data-month') || at.getDate() != mostRecentDay.attr('data-day')) { + if (mostRecentDay.length == 0 || at.month() != mostRecentDay.attr('data-month') || at.date() != mostRecentDay.attr('data-day')) { var day_html = $(history_day_default_html); day_html - .attr('data-month', at.getMonth()) - .attr('data-day', at.getDate()) - .text(at.getDate()+' '+months[at.getMonth()]); + .attr('data-month', at.month()) + .attr('data-day', at.date()) + .text(at.format('D MMMM')); history_container.prepend(day_html); } } diff --git a/kfet/views.py b/kfet/views.py index 2b7ed195..009cb800 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -984,7 +984,7 @@ def kpsul_history(request): 'id', 'amount', 'at', 'checkout_id', 'is_cof', 'valid_by__trigramme', 'on_acc__trigramme') .select_related('valid_by', 'on_acc') - .filter(at__gt=timezone.now()-timedelta(hours=4))) + .filter(at__gt=timezone.now()-timedelta(hours=24))) opegroups = { opegroup['id']:opegroup for opegroup in opegroups_list } for opegroup in opegroups: opegroups[opegroup]['opes'] = [] From bbbfd4aef52416a98becb2b512b4a92a94f60453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 04:46:28 +0200 Subject: [PATCH 101/192] Moment.js sur les dates d'annulation K-Psul --- kfet/templates/kfet/kpsul.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 4e85eaf0..4007b286 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -987,11 +987,11 @@ $(document).ready(function() { if (ope['canceled_at']) { ope_html.addClass('canceled'); - if (ope['canceled_by__trigramme']) { - var cancel = 'Annulé par '+ope['canceled_by__trigramme']+' le '+ope['canceled_at']; - } else { - var cancel = 'Annulé le '+ope['canceled_at']; - } + var cancel = 'Annulé'; + if (ope['canceled_by__trigramme']) + cancel += ' par '+ope['canceled_by__trigramme']; + var canceled_at = moment.tz(ope['canceled_at'],'UTC'); + cancel += ' le '+canceled_at.tz('Europe/Paris').format('DD/MM/YY à HH:mm:ss'); ope_html.find('.canceled').text(cancel); } @@ -1182,11 +1182,11 @@ $(document).ready(function() { function cancelOpe(ope) { var ope_html = history_container.find('[data-ope='+ope['id']+']'); ope_html.addClass('canceled'); - if (ope['canceled_by__trigramme']) { - var cancel = 'Annulé par '+ope['canceled_by__trigramme']+' le '+ope['canceled_at']; - } else { - var cancel = 'Annulé le '+ope['canceled_at']; - } + var cancel = 'Annulé'; + if (ope['canceled_by__trigramme']) + cancel += ' par '+ope['canceled_by__trigramme']; + var canceled_at = moment.tz(ope['canceled_at'], 'UTC'); + cancel += ' le '+canceled_at.tz('Europe/Paris').format('DD/MM/YY à HH:mm:ss'); ope_html.find('.canceled').text(cancel); } From 9e66137c09500894319381f2e27b3d47daff445d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 15:43:16 +0200 Subject: [PATCH 102/192] Commandes sur #13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Un commentaire est demandé. Une permission est nécessaire (afin d'enregistrer la personne ayant enregistré la commande) - Fix annulation K-Psul. Appuyer sur Suppr appelait tout le temps `cancelOperations` même si aucune opération à supprimer n'était sélectionné. --- kfet/forms.py | 2 +- kfet/migrations/0035_auto_20160823_1505.py | 18 ++++++++ kfet/models.py | 9 +++- kfet/templates/kfet/kpsul.html | 48 ++++++++++++++++++---- kfet/views.py | 6 +++ 5 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 kfet/migrations/0035_auto_20160823_1505.py diff --git a/kfet/forms.py b/kfet/forms.py index 71f5a78e..e39ff9e1 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -222,7 +222,7 @@ class KPsulOperationGroupForm(forms.ModelForm): widget = forms.HiddenInput()) class Meta: model = OperationGroup - fields = ['on_acc', 'checkout'] + fields = ['on_acc', 'checkout', 'comment'] widgets = { 'on_acc' : forms.HiddenInput(), } diff --git a/kfet/migrations/0035_auto_20160823_1505.py b/kfet/migrations/0035_auto_20160823_1505.py new file mode 100644 index 00000000..5fd73ae8 --- /dev/null +++ b/kfet/migrations/0035_auto_20160823_1505.py @@ -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'))}, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 52862f1b..1b03ed8b 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -86,6 +86,10 @@ class Account(models.Model): 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 } @@ -109,13 +113,13 @@ class Account(models.Model): 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: - print(new_balance) - print(amount) # Retrieving overdraft amount limit if (hasattr(self, 'negative') and self.negative.authz_overdraft_amount is not None): @@ -518,6 +522,7 @@ class GlobalPermissions(models.Model): ('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'), ) class Settings(models.Model): diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 4007b286..c27d7f27 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -21,8 +21,6 @@ {% csrf_token %} -
      {{ operationgroup_form }}
      -
      @@ -100,9 +98,8 @@
      - + + {% endifchanged %}
      diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index c27d7f27..19fca28f 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -980,7 +980,7 @@ $(document).ready(function() { // ----- var history_container = $('#history'); - var history_operationgroup_html = '
      '; + var history_operationgroup_html = '
      '; var history_operation_html = '
      '; function getOpeHtml(ope, is_cof=false, trigramme='') { @@ -1040,13 +1040,15 @@ $(document).ready(function() { var valid_by = ''; if (opegroup['valid_by__trigramme']) valid_by = 'Par '+opegroup['valid_by__trigramme']; + var comment = opegroup['comment'] || ''; opegroup_html .attr('data-opegroup', opegroup['id']) .find('.time').text(at_formated).end() .find('.amount').text(amount).end() .find('.trigramme').text(trigramme).end() - .find('.valid_by').text(valid_by).end(); + .find('.valid_by').text(valid_by).end() + .find('.comment').text(comment).end(); return opegroup_html; } diff --git a/kfet/views.py b/kfet/views.py index f91bcf95..ff477a3c 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -794,6 +794,7 @@ def kpsul_perform_operations(request): 'checkout__name': operationgroup.checkout.name, 'at': operationgroup.at, 'is_cof': operationgroup.is_cof, + 'comment': operationgroup.comment, 'valid_by__trigramme': ( operationgroup.valid_by and operationgroup.valid_by.trigramme or None), 'on_acc__trigramme': operationgroup.on_acc.trigramme, @@ -987,7 +988,7 @@ def kpsul_cancel_operations(request): def kpsul_history(request): opegroups_list = (OperationGroup.objects .values( - 'id', 'amount', 'at', 'checkout_id', 'is_cof', + 'id', 'amount', 'at', 'checkout_id', 'is_cof', 'comment', 'valid_by__trigramme', 'on_acc__trigramme') .select_related('valid_by', 'on_acc') .filter(at__gt=timezone.now()-timedelta(hours=24))) From 79455193cafde2b5c3448194e4c42e102a96b658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 18:15:41 +0200 Subject: [PATCH 104/192] =?UTF-8?q?Affichage=20d=C3=A9tail=20compte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout de l'affichage des totaux venant des majorations/jour - Fix affichage valid_by historique --- kfet/templates/kfet/account_read.html | 14 +++++++++++++- kfet/views.py | 14 +++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index 977b7c22..ffe77a3c 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -28,6 +28,18 @@
      {% include "kfet/base_messages.html" %}
      + {% if addcosts %} +
      +

      Gagné des majorations

      +
      +
        + {% for addcost in addcosts %} +
      • {{ addcost.date|date:'l j F' }}: {{ addcost.sum_addcosts }}
      • + {% endfor %} +
      +
      +
      + {% endif %}

      Historique

      @@ -48,7 +60,7 @@ {{ ope.group.amount|ukf:ope.group.is_cof }} {% endif %} - {% if perms.kfet.is_team %} + {% if perms.kfet.is_team and ope.group.valid_by %} Par {{ ope.group.valid_by.trigramme }} {% endif %} {% if ope.group.comment %} diff --git a/kfet/views.py b/kfet/views.py index ff477a3c..013031dd 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -12,7 +12,7 @@ from django.contrib.auth.models import User, Permission, Group from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory from django.db import IntegrityError, transaction -from django.db.models import F +from django.db.models import F, Sum from django.utils import timezone from django.utils.crypto import get_random_string from gestioncof.models import CofProfile, Clipper @@ -21,7 +21,7 @@ from kfet.models import (Account, Checkout, Article, Settings, AccountNegative, from kfet.forms import * from collections import defaultdict from kfet import consumers -from datetime import timedelta +import datetime import django_cas_ng @login_required @@ -209,9 +209,17 @@ def account_read(request, trigramme): .filter(group__on_acc=account) .order_by('-group__at')) + addcosts = (OperationGroup.objects + .filter(opes__addcost_for=account,opes__canceled_at=None) + .extra({'date':"date(at)"}) + .values('date') + .annotate(sum_addcosts=Sum('opes__addcost_amount')) + .order_by('-date')) + return render(request, "kfet/account_read.html", { 'account' : account, 'history' : history, + 'addcosts': addcosts, }) # Account - Update @@ -991,7 +999,7 @@ def kpsul_history(request): 'id', 'amount', 'at', 'checkout_id', 'is_cof', 'comment', 'valid_by__trigramme', 'on_acc__trigramme') .select_related('valid_by', 'on_acc') - .filter(at__gt=timezone.now()-timedelta(hours=24))) + .filter(at__gt=timezone.now()-datetime.timedelta(hours=24))) opegroups = { opegroup['id']:opegroup for opegroup in opegroups_list } for opegroup in opegroups: opegroups[opegroup]['opes'] = [] From 54ff265b0f27f0be5e8d428239f88208de5f7eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 23 Aug 2016 20:31:31 +0200 Subject: [PATCH 105/192] =?UTF-8?q?Affichage=20n=C3=A9gatifs=20centralis?= =?UTF-8?q?=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Accessibles depuis la page des comtpes et avec la perm `kfet.view_negs` - Ajout du js moment avec timezone oublié précédemment --- kfet/migrations/0036_auto_20160823_1910.py | 18 + kfet/models.py | 1 + kfet/static/kfet/css/index.css | 10 +- kfet/templates/kfet/account.html | 6 +- kfet/templates/kfet/account_negative.html | 76 ++ kfet/templates/kfet/account_read.html | 2 +- kfet/templates/kfet/base_nav.html | 2 +- kfet/templates/kfet/left_account.html | 2 +- kfet/templates/kfet/left_checkout.html | 2 +- kfet/urls.py | 4 + kfet/views.py | 22 + static/moment-timezone-with-data-2010-2020.js | 1196 +++++++++++++++++ 12 files changed, 1334 insertions(+), 7 deletions(-) create mode 100644 kfet/migrations/0036_auto_20160823_1910.py create mode 100644 kfet/templates/kfet/account_negative.html create mode 100644 static/moment-timezone-with-data-2010-2020.js diff --git a/kfet/migrations/0036_auto_20160823_1910.py b/kfet/migrations/0036_auto_20160823_1910.py new file mode 100644 index 00000000..2d29fd7a --- /dev/null +++ b/kfet/migrations/0036_auto_20160823_1910.py @@ -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'))}, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 1b03ed8b..a2d50cdc 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -523,6 +523,7 @@ class GlobalPermissions(models.Model): ('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'), ) class Settings(models.Model): diff --git a/kfet/static/kfet/css/index.css b/kfet/static/kfet/css/index.css index 315dad1b..72410267 100644 --- a/kfet/static/kfet/css/index.css +++ b/kfet/static/kfet/css/index.css @@ -74,7 +74,7 @@ a:focus, a:hover { } .content-left .block { - padding-top:10px; + padding-top:25px; } .content-left .block .line { @@ -82,13 +82,19 @@ a:focus, a:hover { line-height:30px; } -.content-left .line.trigramme { +.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; diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html index 71d9f350..7983d3a6 100644 --- a/kfet/templates/kfet/account.html +++ b/kfet/templates/kfet/account.html @@ -9,11 +9,15 @@
      -
      {{ accounts|length|add:-1 }} comptes
      +
      {{ accounts|length|add:-1 }}
      +
      compte{{ accounts|length|add:-1|pluralize }}
      Créer un compte Permissions + {% if perms.kfet.view_negs %} + Négatifs + {% endif %}
      diff --git a/kfet/templates/kfet/account_negative.html b/kfet/templates/kfet/account_negative.html new file mode 100644 index 00000000..096fbcb2 --- /dev/null +++ b/kfet/templates/kfet/account_negative.html @@ -0,0 +1,76 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Comptes en négatifs{% endblock %} +{% block content-header-title %}Comptes - Négatifs{% endblock %} + +{% block content %} + +
      +
      +
      +
      +
      {{ negatives|length }}
      +
      compte{{ negatives|length|pluralize }} en négatif
      +
      +
      Total: {{ negatives_sum|floatformat:2 }}€
      +
      +
      +
      Découvert autorisé par défaut
      +
      Montant: {{ settings.overdraft_amount }}€
      +
      Pendant: {{ settings.overdraft_duration }}
      +
      +
      + {% if perms.kfet.change_settings %} + + {% endif %} +
      +
      +
      + {% include 'kfet/base_messages.html' %} +
      +
      +

      Liste des comptes en négatifs

      +
      + + + + + + + + + + + + + {% for neg in negatives %} + + + + + + + + + + + + {% endfor %} +
      TriNomBalanceRéelleDébutDécouvert autoriséJusqu'auBalance offset
      + + + + {{ neg.account.trigramme }}{{ neg.account.name }}{{ neg.account.balance|floatformat:2 }}€ + {% if neg.account.balance_offset %} + {{ neg.account.real_balance|floatformat:2 }}€ + {% endif %} + {{ neg.start|date:'d/m/Y H:i:s'}}{{ neg.authz_overdraft_amount|default_if_none:'' }}{{ neg.authz_overdrafy_until|default_if_none:'' }}{{ neg.balance_offset|default_if_none:'' }}
      +
      +
      +
      +
      +
      + +{% endblock %} diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index ffe77a3c..8341becf 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -34,7 +34,7 @@
        {% for addcost in addcosts %} -
      • {{ addcost.date|date:'l j F' }}: {{ addcost.sum_addcosts }}
      • +
      • {{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€
      • {% endfor %}
      diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index 4e4a6345..b4169824 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -23,7 +23,7 @@ {% endif %} {% if user.profile.account_kfet %}
    • - Mes infos + Mes infos
    • {% endif %} {% if perms.kfet.is_team %} diff --git a/kfet/templates/kfet/left_account.html b/kfet/templates/kfet/left_account.html index e379adfb..19352728 100644 --- a/kfet/templates/kfet/left_account.html +++ b/kfet/templates/kfet/left_account.html @@ -1,7 +1,7 @@ {% load kfet_tags %}
      -
      {{ account.trigramme }}
      +
      {{ account.trigramme }}
      {{ account.balance|ukf:account.is_cof }} UKF
      {{ account.name }}
      diff --git a/kfet/templates/kfet/left_checkout.html b/kfet/templates/kfet/left_checkout.html index 477572b7..e00eede8 100644 --- a/kfet/templates/kfet/left_checkout.html +++ b/kfet/templates/kfet/left_checkout.html @@ -1,5 +1,5 @@
      -
      {{ checkout.name }}
      +
      {{ checkout.name }}
      {{ checkout.balance|floatformat:2 }} €
      Valide du {{ checkout.valid_from|date:'l j F Y, G:i' }}
      diff --git a/kfet/urls.py b/kfet/urls.py index 7b154c35..f444d540 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -49,6 +49,10 @@ urlpatterns = [ permission_required('kfet.manage_perms')(views.AccountGroupUpdate.as_view()), name = 'kfet.account.group.update'), + url(r'^accounts/negatives$', + permission_required('kfet.view_negs')(views.AccountNegativeList.as_view()), + name = 'kfet.account.negative'), + # ----- # Checkout urls # ----- diff --git a/kfet/views.py b/kfet/views.py index 013031dd..f46ece36 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -13,6 +13,7 @@ from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory from django.db import IntegrityError, transaction from django.db.models import F, Sum +from django.db.models.functions import Coalesce from django.utils import timezone from django.utils.crypto import get_random_string from gestioncof.models import CofProfile, Clipper @@ -351,6 +352,27 @@ class AccountGroupUpdate(UpdateView): success_message = 'Groupe modifié : %(name)s' success_url = reverse_lazy('kfet.account.group') +class AccountNegativeList(ListView): + queryset = (AccountNegative.objects + .select_related('account', 'account__cofprofile__user')) + template_name = 'kfet/account_negative.html' + context_object_name = 'negatives' + + def get_context_data(self, **kwargs): + context = super(AccountNegativeList, self).get_context_data(**kwargs) + context['settings'] = { + 'overdraft_amount': Settings.OVERDRAFT_AMOUNT(), + 'overdraft_duration': Settings.OVERDRAFT_DURATION(), + } + negs_sum = (AccountNegative.objects + .aggregate( + bal = Coalesce(Sum('account__balance'),0), + offset = Coalesce(Sum('balance_offset'),0), + ) + ) + context['negatives_sum'] = negs_sum['bal'] + negs_sum['offset'] + return context + # ----- # Checkout views # ----- diff --git a/static/moment-timezone-with-data-2010-2020.js b/static/moment-timezone-with-data-2010-2020.js new file mode 100644 index 00000000..7e58bb50 --- /dev/null +++ b/static/moment-timezone-with-data-2010-2020.js @@ -0,0 +1,1196 @@ +//! moment-timezone.js +//! version : 0.5.5 +//! author : Tim Wood +//! license : MIT +//! github.com/moment/moment-timezone + +(function (root, factory) { + "use strict"; + + /*global define*/ + if (typeof define === 'function' && define.amd) { + define(['moment'], factory); // AMD + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(require('moment')); // Node + } else { + factory(root.moment); // Browser + } +}(this, function (moment) { + "use strict"; + + // Do not load moment-timezone a second time. + if (moment.tz !== undefined) { + logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion); + return moment; + } + + var VERSION = "0.5.5", + zones = {}, + links = {}, + names = {}, + guesses = {}, + cachedGuess, + + momentVersion = moment.version.split('.'), + major = +momentVersion[0], + minor = +momentVersion[1]; + + // Moment.js version check + if (major < 2 || (major === 2 && minor < 6)) { + logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com'); + } + + /************************************ + Unpacking + ************************************/ + + function charCodeToInt(charCode) { + if (charCode > 96) { + return charCode - 87; + } else if (charCode > 64) { + return charCode - 29; + } + return charCode - 48; + } + + function unpackBase60(string) { + var i = 0, + parts = string.split('.'), + whole = parts[0], + fractional = parts[1] || '', + multiplier = 1, + num, + out = 0, + sign = 1; + + // handle negative numbers + if (string.charCodeAt(0) === 45) { + i = 1; + sign = -1; + } + + // handle digits before the decimal + for (i; i < whole.length; i++) { + num = charCodeToInt(whole.charCodeAt(i)); + out = 60 * out + num; + } + + // handle digits after the decimal + for (i = 0; i < fractional.length; i++) { + multiplier = multiplier / 60; + num = charCodeToInt(fractional.charCodeAt(i)); + out += num * multiplier; + } + + return out * sign; + } + + function arrayToInt (array) { + for (var i = 0; i < array.length; i++) { + array[i] = unpackBase60(array[i]); + } + } + + function intToUntil (array, length) { + for (var i = 0; i < length; i++) { + array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds + } + + array[length - 1] = Infinity; + } + + function mapIndices (source, indices) { + var out = [], i; + + for (i = 0; i < indices.length; i++) { + out[i] = source[indices[i]]; + } + + return out; + } + + function unpack (string) { + var data = string.split('|'), + offsets = data[2].split(' '), + indices = data[3].split(''), + untils = data[4].split(' '); + + arrayToInt(offsets); + arrayToInt(indices); + arrayToInt(untils); + + intToUntil(untils, indices.length); + + return { + name : data[0], + abbrs : mapIndices(data[1].split(' '), indices), + offsets : mapIndices(offsets, indices), + untils : untils, + population : data[5] | 0 + }; + } + + /************************************ + Zone object + ************************************/ + + function Zone (packedString) { + if (packedString) { + this._set(unpack(packedString)); + } + } + + Zone.prototype = { + _set : function (unpacked) { + this.name = unpacked.name; + this.abbrs = unpacked.abbrs; + this.untils = unpacked.untils; + this.offsets = unpacked.offsets; + this.population = unpacked.population; + }, + + _index : function (timestamp) { + var target = +timestamp, + untils = this.untils, + i; + + for (i = 0; i < untils.length; i++) { + if (target < untils[i]) { + return i; + } + } + }, + + parse : function (timestamp) { + var target = +timestamp, + offsets = this.offsets, + untils = this.untils, + max = untils.length - 1, + offset, offsetNext, offsetPrev, i; + + for (i = 0; i < max; i++) { + offset = offsets[i]; + offsetNext = offsets[i + 1]; + offsetPrev = offsets[i ? i - 1 : i]; + + if (offset < offsetNext && tz.moveAmbiguousForward) { + offset = offsetNext; + } else if (offset > offsetPrev && tz.moveInvalidForward) { + offset = offsetPrev; + } + + if (target < untils[i] - (offset * 60000)) { + return offsets[i]; + } + } + + return offsets[max]; + }, + + abbr : function (mom) { + return this.abbrs[this._index(mom)]; + }, + + offset : function (mom) { + return this.offsets[this._index(mom)]; + } + }; + + /************************************ + Current Timezone + ************************************/ + + function OffsetAt(at) { + var timeString = at.toTimeString(); + var abbr = timeString.match(/\([a-z ]+\)/i); + if (abbr && abbr[0]) { + // 17:56:31 GMT-0600 (CST) + // 17:56:31 GMT-0600 (Central Standard Time) + abbr = abbr[0].match(/[A-Z]/g); + abbr = abbr ? abbr.join('') : undefined; + } else { + // 17:56:31 CST + // 17:56:31 GMT+0800 (台北標準時間) + abbr = timeString.match(/[A-Z]{3,5}/g); + abbr = abbr ? abbr[0] : undefined; + } + + if (abbr === 'GMT') { + abbr = undefined; + } + + this.at = +at; + this.abbr = abbr; + this.offset = at.getTimezoneOffset(); + } + + function ZoneScore(zone) { + this.zone = zone; + this.offsetScore = 0; + this.abbrScore = 0; + } + + ZoneScore.prototype.scoreOffsetAt = function (offsetAt) { + this.offsetScore += Math.abs(this.zone.offset(offsetAt.at) - offsetAt.offset); + if (this.zone.abbr(offsetAt.at).replace(/[^A-Z]/g, '') !== offsetAt.abbr) { + this.abbrScore++; + } + }; + + function findChange(low, high) { + var mid, diff; + + while ((diff = ((high.at - low.at) / 12e4 | 0) * 6e4)) { + mid = new OffsetAt(new Date(low.at + diff)); + if (mid.offset === low.offset) { + low = mid; + } else { + high = mid; + } + } + + return low; + } + + function userOffsets() { + var startYear = new Date().getFullYear() - 2, + last = new OffsetAt(new Date(startYear, 0, 1)), + offsets = [last], + change, next, i; + + for (i = 1; i < 48; i++) { + next = new OffsetAt(new Date(startYear, i, 1)); + if (next.offset !== last.offset) { + change = findChange(last, next); + offsets.push(change); + offsets.push(new OffsetAt(new Date(change.at + 6e4))); + } + last = next; + } + + for (i = 0; i < 4; i++) { + offsets.push(new OffsetAt(new Date(startYear + i, 0, 1))); + offsets.push(new OffsetAt(new Date(startYear + i, 6, 1))); + } + + return offsets; + } + + function sortZoneScores (a, b) { + if (a.offsetScore !== b.offsetScore) { + return a.offsetScore - b.offsetScore; + } + if (a.abbrScore !== b.abbrScore) { + return a.abbrScore - b.abbrScore; + } + return b.zone.population - a.zone.population; + } + + function addToGuesses (name, offsets) { + var i, offset; + arrayToInt(offsets); + for (i = 0; i < offsets.length; i++) { + offset = offsets[i]; + guesses[offset] = guesses[offset] || {}; + guesses[offset][name] = true; + } + } + + function guessesForUserOffsets (offsets) { + var offsetsLength = offsets.length, + filteredGuesses = {}, + out = [], + i, j, guessesOffset; + + for (i = 0; i < offsetsLength; i++) { + guessesOffset = guesses[offsets[i].offset] || {}; + for (j in guessesOffset) { + if (guessesOffset.hasOwnProperty(j)) { + filteredGuesses[j] = true; + } + } + } + + for (i in filteredGuesses) { + if (filteredGuesses.hasOwnProperty(i)) { + out.push(names[i]); + } + } + + return out; + } + + function rebuildGuess () { + + // use Intl API when available and returning valid time zone + try { + var intlName = Intl.DateTimeFormat().resolvedOptions().timeZone; + if (intlName){ + var name = names[normalizeName(intlName)]; + if (name) { + return name; + } + logError("Moment Timezone found " + intlName + " from the Intl api, but did not have that data loaded."); + } + } catch (e) { + // Intl unavailable, fall back to manual guessing. + } + + var offsets = userOffsets(), + offsetsLength = offsets.length, + guesses = guessesForUserOffsets(offsets), + zoneScores = [], + zoneScore, i, j; + + for (i = 0; i < guesses.length; i++) { + zoneScore = new ZoneScore(getZone(guesses[i]), offsetsLength); + for (j = 0; j < offsetsLength; j++) { + zoneScore.scoreOffsetAt(offsets[j]); + } + zoneScores.push(zoneScore); + } + + zoneScores.sort(sortZoneScores); + + return zoneScores.length > 0 ? zoneScores[0].zone.name : undefined; + } + + function guess (ignoreCache) { + if (!cachedGuess || ignoreCache) { + cachedGuess = rebuildGuess(); + } + return cachedGuess; + } + + /************************************ + Global Methods + ************************************/ + + function normalizeName (name) { + return (name || '').toLowerCase().replace(/\//g, '_'); + } + + function addZone (packed) { + var i, name, split, normalized; + + if (typeof packed === "string") { + packed = [packed]; + } + + for (i = 0; i < packed.length; i++) { + split = packed[i].split('|'); + name = split[0]; + normalized = normalizeName(name); + zones[normalized] = packed[i]; + names[normalized] = name; + if (split[5]) { + addToGuesses(normalized, split[2].split(' ')); + } + } + } + + function getZone (name, caller) { + name = normalizeName(name); + + var zone = zones[name]; + var link; + + if (zone instanceof Zone) { + return zone; + } + + if (typeof zone === 'string') { + zone = new Zone(zone); + zones[name] = zone; + return zone; + } + + // Pass getZone to prevent recursion more than 1 level deep + if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) { + zone = zones[name] = new Zone(); + zone._set(link); + zone.name = names[name]; + return zone; + } + + return null; + } + + function getNames () { + var i, out = []; + + for (i in names) { + if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) { + out.push(names[i]); + } + } + + return out.sort(); + } + + function addLink (aliases) { + var i, alias, normal0, normal1; + + if (typeof aliases === "string") { + aliases = [aliases]; + } + + for (i = 0; i < aliases.length; i++) { + alias = aliases[i].split('|'); + + normal0 = normalizeName(alias[0]); + normal1 = normalizeName(alias[1]); + + links[normal0] = normal1; + names[normal0] = alias[0]; + + links[normal1] = normal0; + names[normal1] = alias[1]; + } + } + + function loadData (data) { + addZone(data.zones); + addLink(data.links); + tz.dataVersion = data.version; + } + + function zoneExists (name) { + if (!zoneExists.didShowError) { + zoneExists.didShowError = true; + logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')"); + } + return !!getZone(name); + } + + function needsOffset (m) { + return !!(m._a && (m._tzm === undefined)); + } + + function logError (message) { + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error(message); + } + } + + /************************************ + moment.tz namespace + ************************************/ + + function tz (input) { + var args = Array.prototype.slice.call(arguments, 0, -1), + name = arguments[arguments.length - 1], + zone = getZone(name), + out = moment.utc.apply(null, args); + + if (zone && !moment.isMoment(input) && needsOffset(out)) { + out.add(zone.parse(out), 'minutes'); + } + + out.tz(name); + + return out; + } + + tz.version = VERSION; + tz.dataVersion = ''; + tz._zones = zones; + tz._links = links; + tz._names = names; + tz.add = addZone; + tz.link = addLink; + tz.load = loadData; + tz.zone = getZone; + tz.zoneExists = zoneExists; // deprecated in 0.1.0 + tz.guess = guess; + tz.names = getNames; + tz.Zone = Zone; + tz.unpack = unpack; + tz.unpackBase60 = unpackBase60; + tz.needsOffset = needsOffset; + tz.moveInvalidForward = true; + tz.moveAmbiguousForward = false; + + /************************************ + Interface with Moment.js + ************************************/ + + var fn = moment.fn; + + moment.tz = tz; + + moment.defaultZone = null; + + moment.updateOffset = function (mom, keepTime) { + var zone = moment.defaultZone, + offset; + + if (mom._z === undefined) { + if (zone && needsOffset(mom) && !mom._isUTC) { + mom._d = moment.utc(mom._a)._d; + mom.utc().add(zone.parse(mom), 'minutes'); + } + mom._z = zone; + } + if (mom._z) { + offset = mom._z.offset(mom); + if (Math.abs(offset) < 16) { + offset = offset / 60; + } + if (mom.utcOffset !== undefined) { + mom.utcOffset(-offset, keepTime); + } else { + mom.zone(offset, keepTime); + } + } + }; + + fn.tz = function (name) { + if (name) { + this._z = getZone(name); + if (this._z) { + moment.updateOffset(this); + } else { + logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/."); + } + return this; + } + if (this._z) { return this._z.name; } + }; + + function abbrWrap (old) { + return function () { + if (this._z) { return this._z.abbr(this); } + return old.call(this); + }; + } + + function resetZoneWrap (old) { + return function () { + this._z = null; + return old.apply(this, arguments); + }; + } + + fn.zoneName = abbrWrap(fn.zoneName); + fn.zoneAbbr = abbrWrap(fn.zoneAbbr); + fn.utc = resetZoneWrap(fn.utc); + + moment.tz.setDefault = function(name) { + if (major < 2 || (major === 2 && minor < 9)) { + logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.'); + } + moment.defaultZone = name ? getZone(name) : null; + return moment; + }; + + // Cloning a moment should include the _z property. + var momentProperties = moment.momentProperties; + if (Object.prototype.toString.call(momentProperties) === '[object Array]') { + // moment 2.8.1+ + momentProperties.push('_z'); + momentProperties.push('_a'); + } else if (momentProperties) { + // moment 2.7.0 + momentProperties._z = null; + } + + loadData({ + "version": "2016f", + "zones": [ + "Africa/Abidjan|GMT|0|0||48e5", + "Africa/Khartoum|EAT|-30|0||51e5", + "Africa/Algiers|CET|-10|0||26e5", + "Africa/Lagos|WAT|-10|0||17e6", + "Africa/Maputo|CAT|-20|0||26e5", + "Africa/Cairo|EET EEST|-20 -30|010101010|1Cby0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0|15e6", + "Africa/Casablanca|WET WEST|0 -10|01010101010101010101010101010101010101010|1Cco0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0|32e5", + "Europe/Paris|CET CEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e6", + "Africa/Johannesburg|SAST|-20|0||84e5", + "Africa/Tripoli|EET CET CEST|-20 -10 -20|0120|1IlA0 TA0 1o00|11e5", + "Africa/Windhoek|WAST WAT|-20 -10|01010101010101010101010|1C1c0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0|32e4", + "America/Adak|HST HDT|a0 90|01010101010101010101010|1BR00 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|326", + "America/Anchorage|AKST AKDT|90 80|01010101010101010101010|1BQX0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|30e4", + "America/Santo_Domingo|AST|40|0||29e5", + "America/Araguaina|BRT BRST|30 20|010|1IdD0 Lz0|14e4", + "America/Argentina/Buenos_Aires|ART|30|0|", + "America/Asuncion|PYST PYT|30 40|01010101010101010101010|1C430 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0|28e5", + "America/Panama|EST|50|0||15e5", + "America/Bahia|BRT BRST|30 20|010|1FJf0 Rb0|27e5", + "America/Bahia_Banderas|MST CDT CST|70 50 60|01212121212121212121212|1C1l0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|84e3", + "America/Fortaleza|BRT|30|0||34e5", + "America/Managua|CST|60|0||22e5", + "America/Manaus|AMT|40|0||19e5", + "America/Bogota|COT|50|0||90e5", + "America/Denver|MST MDT|70 60|01010101010101010101010|1BQV0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e5", + "America/Campo_Grande|AMST AMT|30 40|01010101010101010101010|1BIr0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10|77e4", + "America/Cancun|CST CDT EST|60 50 50|010101010102|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4", + "America/Caracas|VET VET|4u 40|01|1QMT0|29e5", + "America/Cayenne|GFT|30|0||58e3", + "America/Chicago|CST CDT|60 50|01010101010101010101010|1BQU0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|92e5", + "America/Chihuahua|MST MDT|70 60|01010101010101010101010|1C1l0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|81e4", + "America/Phoenix|MST|70|0||42e5", + "America/Los_Angeles|PST PDT|80 70|01010101010101010101010|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e6", + "America/New_York|EST EDT|50 40|01010101010101010101010|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6", + "America/Rio_Branco|AMT ACT|40 50|01|1KLE0|31e4", + "America/Fort_Nelson|PST PDT MST|80 70 70|010101010102|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2", + "America/Halifax|AST ADT|40 30|01010101010101010101010|1BQS0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|39e4", + "America/Godthab|WGT WGST|30 20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e3", + "America/Goose_Bay|AST ADT|40 30|01010101010101010101010|1BQQ1 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|76e2", + "America/Grand_Turk|EST EDT AST|50 40 40|0101010101012|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e2", + "America/Guayaquil|ECT|50|0||27e5", + "America/Guyana|GYT|40|0||80e4", + "America/Havana|CST CDT|50 40|01010101010101010101010|1BQR0 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0|21e5", + "America/La_Paz|BOT|40|0||19e5", + "America/Lima|PET|50|0||11e6", + "America/Mexico_City|CST CDT|60 50|01010101010101010101010|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|20e6", + "America/Metlakatla|PST AKST AKDT|80 90 80|012121212121|1PAa0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2", + "America/Miquelon|PMST PMDT|30 20|01010101010101010101010|1BQR0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|61e2", + "America/Montevideo|UYST UYT|20 30|010101010101|1BQQ0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0|17e5", + "America/Noronha|FNT|20|0||30e2", + "America/North_Dakota/Beulah|MST MDT CST CDT|70 60 60 50|01232323232323232323232|1BQV0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Paramaribo|SRT|30|0||24e4", + "America/Port-au-Prince|EST EDT|50 40|010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5", + "America/Santiago|CLST CLT|30 40|010101010101010101010|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|62e5", + "America/Sao_Paulo|BRST BRT|20 30|01010101010101010101010|1BIq0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10|20e6", + "America/Scoresbysund|EGT EGST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|452", + "America/St_Johns|NST NDT|3u 2u|01010101010101010101010|1BQPv 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4", + "Antarctica/Casey|CAST AWST|-b0 -80|0101|1BN30 40P0 KL0|10", + "Antarctica/Davis|DAVT DAVT|-50 -70|0101|1BPw0 3Wn0 KN0|70", + "Antarctica/DumontDUrville|DDUT|-a0|0||80", + "Antarctica/Macquarie|AEDT MIST|-b0 -b0|01|1C140|1", + "Antarctica/Mawson|MAWT|-50|0||60", + "Pacific/Auckland|NZDT NZST|-d0 -c0|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|14e5", + "Antarctica/Rothera|ROTT|30|0||130", + "Antarctica/Syowa|SYOT|-30|0||20", + "Antarctica/Troll|UTC CEST|0 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|40", + "Antarctica/Vostok|VOST|-60|0||25", + "Asia/Baghdad|AST|-30|0||66e5", + "Asia/Almaty|+06|-60|0||15e5", + "Asia/Amman|EET EEST|-20 -30|010101010101010101010|1BVy0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0|25e5", + "Asia/Anadyr|ANAT ANAST ANAT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0|13e3", + "Asia/Aqtobe|+05|-50|0||27e4", + "Asia/Ashgabat|TMT|-50|0||41e4", + "Asia/Baku|AZT AZST|-40 -50|0101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5", + "Asia/Bangkok|ICT|-70|0||15e6", + "Asia/Barnaul|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 3rd0", + "Asia/Beirut|EET EEST|-20 -30|01010101010101010101010|1BWm0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0|22e5", + "Asia/Bishkek|KGT|-60|0||87e4", + "Asia/Brunei|BNT|-80|0||42e4", + "Asia/Kolkata|IST|-5u|0||15e6", + "Asia/Chita|YAKT YAKST YAKT IRKT|-90 -a0 -a0 -80|010230|1BWh0 1qM0 WM0 8Hz0 3re0|33e4", + "Asia/Choibalsan|CHOT CHOST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|38e3", + "Asia/Shanghai|CST|-80|0||23e6", + "Asia/Dhaka|BDT|-60|0||16e6", + "Asia/Damascus|EET EEST|-20 -30|01010101010101010101010|1C0m0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0|26e5", + "Asia/Dili|TLT|-90|0||19e4", + "Asia/Dubai|GST|-40|0||39e5", + "Asia/Dushanbe|TJT|-50|0||76e4", + "Asia/Gaza|EET EEST|-20 -30|01010101010101010101010|1BVW1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|18e5", + "Asia/Hebron|EET EEST|-20 -30|0101010101010101010101010|1BVy0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|25e4", + "Asia/Hong_Kong|HKT|-80|0||73e5", + "Asia/Hovd|HOVT HOVST|-70 -80|0101010101010|1O8H0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|81e3", + "Asia/Irkutsk|IRKT IRKST IRKT|-80 -90 -90|01020|1BWi0 1qM0 WM0 8Hz0|60e4", + "Europe/Istanbul|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|13e6", + "Asia/Jakarta|WIB|-70|0||31e6", + "Asia/Jayapura|WIT|-90|0||26e4", + "Asia/Jerusalem|IST IDT|-20 -30|01010101010101010101010|1BVA0 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0|81e4", + "Asia/Kabul|AFT|-4u|0||46e5", + "Asia/Kamchatka|PETT PETST PETT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0|18e4", + "Asia/Karachi|PKT|-50|0||24e6", + "Asia/Urumqi|XJT|-60|0||32e5", + "Asia/Kathmandu|NPT|-5J|0||12e5", + "Asia/Khandyga|VLAT VLAST VLAT YAKT YAKT|-a0 -b0 -b0 -a0 -90|010234|1BWg0 1qM0 WM0 17V0 7zD0|66e2", + "Asia/Krasnoyarsk|KRAT KRAST KRAT|-70 -80 -80|01020|1BWj0 1qM0 WM0 8Hz0|10e5", + "Asia/Kuala_Lumpur|MYT|-80|0||71e5", + "Asia/Magadan|MAGT MAGST MAGT MAGT|-b0 -c0 -c0 -a0|010230|1BWf0 1qM0 WM0 8Hz0 3Cq0|95e3", + "Asia/Makassar|WITA|-80|0||15e5", + "Asia/Manila|PHT|-80|0||24e6", + "Europe/Athens|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|35e5", + "Asia/Novokuznetsk|+07 +06|-70 -60|010|1Dp80 WM0|55e4", + "Asia/Novosibirsk|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 4eN0|15e5", + "Asia/Omsk|OMST OMSST OMST|-60 -70 -70|01020|1BWk0 1qM0 WM0 8Hz0|12e5", + "Asia/Pyongyang|KST KST|-90 -8u|01|1P4D0|29e5", + "Asia/Rangoon|MMT|-6u|0||48e5", + "Asia/Sakhalin|SAKT SAKST SAKT|-a0 -b0 -b0|010202|1BWg0 1qM0 WM0 8Hz0 3rd0|58e4", + "Asia/Tashkent|UZT|-50|0||23e5", + "Asia/Seoul|KST|-90|0||23e6", + "Asia/Singapore|SGT|-80|0||56e5", + "Asia/Srednekolymsk|MAGT MAGST MAGT SRET|-b0 -c0 -c0 -b0|01023|1BWf0 1qM0 WM0 8Hz0|35e2", + "Asia/Tbilisi|GET|-40|0||11e5", + "Asia/Tehran|IRST IRDT|-3u -4u|01010101010101010101010|1BTUu 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0|14e6", + "Asia/Thimphu|BTT|-60|0||79e3", + "Asia/Tokyo|JST|-90|0||38e6", + "Asia/Tomsk|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 3Qp0|10e5", + "Asia/Ulaanbaatar|ULAT ULAST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|12e5", + "Asia/Ust-Nera|MAGT MAGST MAGT VLAT VLAT|-b0 -c0 -c0 -b0 -a0|010234|1BWf0 1qM0 WM0 17V0 7zD0|65e2", + "Asia/Vladivostok|VLAT VLAST VLAT|-a0 -b0 -b0|01020|1BWg0 1qM0 WM0 8Hz0|60e4", + "Asia/Yakutsk|YAKT YAKST YAKT|-90 -a0 -a0|01020|1BWh0 1qM0 WM0 8Hz0|28e4", + "Asia/Yekaterinburg|YEKT YEKST YEKT|-50 -60 -60|01020|1BWl0 1qM0 WM0 8Hz0|14e5", + "Asia/Yerevan|AMT AMST|-40 -50|01010|1BWm0 1qM0 WM0 1qM0|13e5", + "Atlantic/Azores|AZOT AZOST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|25e4", + "Europe/Lisbon|WET WEST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e5", + "Atlantic/Cape_Verde|CVT|10|0||50e4", + "Atlantic/South_Georgia|GST|20|0||30", + "Atlantic/Stanley|FKST FKT|30 40|010|1C6R0 U10|21e2", + "Australia/Sydney|AEDT AEST|-b0 -a0|01010101010101010101010|1C140 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|40e5", + "Australia/Adelaide|ACDT ACST|-au -9u|01010101010101010101010|1C14u 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|11e5", + "Australia/Brisbane|AEST|-a0|0||20e5", + "Australia/Darwin|ACST|-9u|0||12e4", + "Australia/Eucla|ACWST|-8J|0||368", + "Australia/Lord_Howe|LHDT LHST|-b0 -au|01010101010101010101010|1C130 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu|347", + "Australia/Perth|AWST|-80|0||18e5", + "Pacific/Easter|EASST EAST|50 60|010101010101010101010|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|30e2", + "Europe/Dublin|GMT IST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", + "Etc/GMT+1|GMT+1|10|0|", + "Etc/GMT+10|GMT+10|a0|0|", + "Etc/GMT+11|GMT+11|b0|0|", + "Etc/GMT+12|GMT+12|c0|0|", + "Etc/GMT+2|GMT+2|20|0|", + "Etc/GMT+3|GMT+3|30|0|", + "Etc/GMT+4|GMT+4|40|0|", + "Etc/GMT+5|GMT+5|50|0|", + "Etc/GMT+6|GMT+6|60|0|", + "Etc/GMT+7|GMT+7|70|0|", + "Etc/GMT+8|GMT+8|80|0|", + "Etc/GMT+9|GMT+9|90|0|", + "Etc/GMT-1|GMT-1|-10|0|", + "Etc/GMT-10|GMT-10|-a0|0|", + "Etc/GMT-11|GMT-11|-b0|0|", + "Etc/GMT-12|GMT-12|-c0|0|", + "Etc/GMT-13|GMT-13|-d0|0|", + "Etc/GMT-14|GMT-14|-e0|0|", + "Etc/GMT-2|GMT-2|-20|0|", + "Etc/GMT-3|GMT-3|-30|0|", + "Etc/GMT-4|GMT-4|-40|0|", + "Etc/GMT-5|GMT-5|-50|0|", + "Etc/GMT-6|GMT-6|-60|0|", + "Etc/GMT-7|GMT-7|-70|0|", + "Etc/GMT-8|GMT-8|-80|0|", + "Etc/GMT-9|GMT-9|-90|0|", + "Etc/UCT|UCT|0|0|", + "Etc/UTC|UTC|0|0|", + "Europe/Astrakhan|+03 +04|-30 -40|010101|1BWn0 1qM0 WM0 8Hz0 3rd0", + "Europe/London|GMT BST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|10e6", + "Europe/Chisinau|EET EEST|-20 -30|01010101010101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|67e4", + "Europe/Kaliningrad|EET EEST FET|-20 -30 -30|01020|1BWo0 1qM0 WM0 8Hz0|44e4", + "Europe/Kirov|+03 +04|-30 -40|01010|1BWn0 1qM0 WM0 8Hz0|48e4", + "Europe/Minsk|EET EEST FET MSK|-20 -30 -30 -30|01023|1BWo0 1qM0 WM0 8Hy0|19e5", + "Europe/Moscow|MSK MSD MSK|-30 -40 -40|01020|1BWn0 1qM0 WM0 8Hz0|16e6", + "Europe/Samara|SAMT SAMST SAMT|-40 -40 -30|0120|1BWm0 1qN0 WM0|12e5", + "Europe/Simferopol|EET EEST MSK MSK|-20 -30 -40 -30|01010101023|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0|33e4", + "Pacific/Honolulu|HST|a0|0||37e4", + "Indian/Chagos|IOT|-60|0||30e2", + "Indian/Christmas|CXT|-70|0||21e2", + "Indian/Cocos|CCT|-6u|0||596", + "Indian/Kerguelen|TFT|-50|0||130", + "Indian/Mahe|SCT|-40|0||79e3", + "Indian/Maldives|MVT|-50|0||35e4", + "Indian/Mauritius|MUT|-40|0||15e4", + "Indian/Reunion|RET|-40|0||84e4", + "Pacific/Majuro|MHT|-c0|0||28e3", + "MET|MET MEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Pacific/Chatham|CHADT CHAST|-dJ -cJ|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|600", + "Pacific/Apia|SST SDT WSDT WSST|b0 a0 -e0 -d0|01012323232323232323232|1Dbn0 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|37e3", + "Pacific/Bougainville|PGT BST|-a0 -b0|01|1NwE0|18e4", + "Pacific/Chuuk|CHUT|-a0|0||49e3", + "Pacific/Efate|VUT|-b0|0||66e3", + "Pacific/Enderbury|PHOT|-d0|0||1", + "Pacific/Fakaofo|TKT TKT|b0 -d0|01|1Gfn0|483", + "Pacific/Fiji|FJST FJT|-d0 -c0|01010101010101010101010|1BWe0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0|88e4", + "Pacific/Funafuti|TVT|-c0|0||45e2", + "Pacific/Galapagos|GALT|60|0||25e3", + "Pacific/Gambier|GAMT|90|0||125", + "Pacific/Guadalcanal|SBT|-b0|0||11e4", + "Pacific/Guam|ChST|-a0|0||17e4", + "Pacific/Kiritimati|LINT|-e0|0||51e2", + "Pacific/Kosrae|KOST|-b0|0||66e2", + "Pacific/Marquesas|MART|9u|0||86e2", + "Pacific/Pago_Pago|SST|b0|0||37e2", + "Pacific/Nauru|NRT|-c0|0||10e3", + "Pacific/Niue|NUT|b0|0||12e2", + "Pacific/Norfolk|NFT NFT|-bu -b0|01|1PoCu|25e4", + "Pacific/Noumea|NCT|-b0|0||98e3", + "Pacific/Palau|PWT|-90|0||21e3", + "Pacific/Pitcairn|PST|80|0||56", + "Pacific/Pohnpei|PONT|-b0|0||34e3", + "Pacific/Port_Moresby|PGT|-a0|0||25e4", + "Pacific/Rarotonga|CKT|a0|0||13e3", + "Pacific/Tahiti|TAHT|a0|0||18e4", + "Pacific/Tarawa|GILT|-c0|0||29e3", + "Pacific/Tongatapu|TOT|-d0|0||75e3", + "Pacific/Wake|WAKT|-c0|0||16e3", + "Pacific/Wallis|WFT|-c0|0||94" + ], + "links": [ + "Africa/Abidjan|Africa/Accra", + "Africa/Abidjan|Africa/Bamako", + "Africa/Abidjan|Africa/Banjul", + "Africa/Abidjan|Africa/Bissau", + "Africa/Abidjan|Africa/Conakry", + "Africa/Abidjan|Africa/Dakar", + "Africa/Abidjan|Africa/Freetown", + "Africa/Abidjan|Africa/Lome", + "Africa/Abidjan|Africa/Monrovia", + "Africa/Abidjan|Africa/Nouakchott", + "Africa/Abidjan|Africa/Ouagadougou", + "Africa/Abidjan|Africa/Sao_Tome", + "Africa/Abidjan|Africa/Timbuktu", + "Africa/Abidjan|America/Danmarkshavn", + "Africa/Abidjan|Atlantic/Reykjavik", + "Africa/Abidjan|Atlantic/St_Helena", + "Africa/Abidjan|Etc/GMT", + "Africa/Abidjan|Etc/GMT+0", + "Africa/Abidjan|Etc/GMT-0", + "Africa/Abidjan|Etc/GMT0", + "Africa/Abidjan|Etc/Greenwich", + "Africa/Abidjan|GMT", + "Africa/Abidjan|GMT+0", + "Africa/Abidjan|GMT-0", + "Africa/Abidjan|GMT0", + "Africa/Abidjan|Greenwich", + "Africa/Abidjan|Iceland", + "Africa/Algiers|Africa/Tunis", + "Africa/Cairo|Egypt", + "Africa/Casablanca|Africa/El_Aaiun", + "Africa/Johannesburg|Africa/Maseru", + "Africa/Johannesburg|Africa/Mbabane", + "Africa/Khartoum|Africa/Addis_Ababa", + "Africa/Khartoum|Africa/Asmara", + "Africa/Khartoum|Africa/Asmera", + "Africa/Khartoum|Africa/Dar_es_Salaam", + "Africa/Khartoum|Africa/Djibouti", + "Africa/Khartoum|Africa/Juba", + "Africa/Khartoum|Africa/Kampala", + "Africa/Khartoum|Africa/Mogadishu", + "Africa/Khartoum|Africa/Nairobi", + "Africa/Khartoum|Indian/Antananarivo", + "Africa/Khartoum|Indian/Comoro", + "Africa/Khartoum|Indian/Mayotte", + "Africa/Lagos|Africa/Bangui", + "Africa/Lagos|Africa/Brazzaville", + "Africa/Lagos|Africa/Douala", + "Africa/Lagos|Africa/Kinshasa", + "Africa/Lagos|Africa/Libreville", + "Africa/Lagos|Africa/Luanda", + "Africa/Lagos|Africa/Malabo", + "Africa/Lagos|Africa/Ndjamena", + "Africa/Lagos|Africa/Niamey", + "Africa/Lagos|Africa/Porto-Novo", + "Africa/Maputo|Africa/Blantyre", + "Africa/Maputo|Africa/Bujumbura", + "Africa/Maputo|Africa/Gaborone", + "Africa/Maputo|Africa/Harare", + "Africa/Maputo|Africa/Kigali", + "Africa/Maputo|Africa/Lubumbashi", + "Africa/Maputo|Africa/Lusaka", + "Africa/Tripoli|Libya", + "America/Adak|America/Atka", + "America/Adak|US/Aleutian", + "America/Anchorage|America/Juneau", + "America/Anchorage|America/Nome", + "America/Anchorage|America/Sitka", + "America/Anchorage|America/Yakutat", + "America/Anchorage|US/Alaska", + "America/Argentina/Buenos_Aires|America/Argentina/Catamarca", + "America/Argentina/Buenos_Aires|America/Argentina/ComodRivadavia", + "America/Argentina/Buenos_Aires|America/Argentina/Cordoba", + "America/Argentina/Buenos_Aires|America/Argentina/Jujuy", + "America/Argentina/Buenos_Aires|America/Argentina/La_Rioja", + "America/Argentina/Buenos_Aires|America/Argentina/Mendoza", + "America/Argentina/Buenos_Aires|America/Argentina/Rio_Gallegos", + "America/Argentina/Buenos_Aires|America/Argentina/Salta", + "America/Argentina/Buenos_Aires|America/Argentina/San_Juan", + "America/Argentina/Buenos_Aires|America/Argentina/San_Luis", + "America/Argentina/Buenos_Aires|America/Argentina/Tucuman", + "America/Argentina/Buenos_Aires|America/Argentina/Ushuaia", + "America/Argentina/Buenos_Aires|America/Buenos_Aires", + "America/Argentina/Buenos_Aires|America/Catamarca", + "America/Argentina/Buenos_Aires|America/Cordoba", + "America/Argentina/Buenos_Aires|America/Jujuy", + "America/Argentina/Buenos_Aires|America/Mendoza", + "America/Argentina/Buenos_Aires|America/Rosario", + "America/Campo_Grande|America/Cuiaba", + "America/Chicago|America/Indiana/Knox", + "America/Chicago|America/Indiana/Tell_City", + "America/Chicago|America/Knox_IN", + "America/Chicago|America/Matamoros", + "America/Chicago|America/Menominee", + "America/Chicago|America/North_Dakota/Center", + "America/Chicago|America/North_Dakota/New_Salem", + "America/Chicago|America/Rainy_River", + "America/Chicago|America/Rankin_Inlet", + "America/Chicago|America/Resolute", + "America/Chicago|America/Winnipeg", + "America/Chicago|CST6CDT", + "America/Chicago|Canada/Central", + "America/Chicago|US/Central", + "America/Chicago|US/Indiana-Starke", + "America/Chihuahua|America/Mazatlan", + "America/Chihuahua|Mexico/BajaSur", + "America/Denver|America/Boise", + "America/Denver|America/Cambridge_Bay", + "America/Denver|America/Edmonton", + "America/Denver|America/Inuvik", + "America/Denver|America/Ojinaga", + "America/Denver|America/Shiprock", + "America/Denver|America/Yellowknife", + "America/Denver|Canada/Mountain", + "America/Denver|MST7MDT", + "America/Denver|Navajo", + "America/Denver|US/Mountain", + "America/Fortaleza|America/Belem", + "America/Fortaleza|America/Maceio", + "America/Fortaleza|America/Recife", + "America/Fortaleza|America/Santarem", + "America/Halifax|America/Glace_Bay", + "America/Halifax|America/Moncton", + "America/Halifax|America/Thule", + "America/Halifax|Atlantic/Bermuda", + "America/Halifax|Canada/Atlantic", + "America/Havana|Cuba", + "America/Los_Angeles|America/Dawson", + "America/Los_Angeles|America/Ensenada", + "America/Los_Angeles|America/Santa_Isabel", + "America/Los_Angeles|America/Tijuana", + "America/Los_Angeles|America/Vancouver", + "America/Los_Angeles|America/Whitehorse", + "America/Los_Angeles|Canada/Pacific", + "America/Los_Angeles|Canada/Yukon", + "America/Los_Angeles|Mexico/BajaNorte", + "America/Los_Angeles|PST8PDT", + "America/Los_Angeles|US/Pacific", + "America/Los_Angeles|US/Pacific-New", + "America/Managua|America/Belize", + "America/Managua|America/Costa_Rica", + "America/Managua|America/El_Salvador", + "America/Managua|America/Guatemala", + "America/Managua|America/Regina", + "America/Managua|America/Swift_Current", + "America/Managua|America/Tegucigalpa", + "America/Managua|Canada/East-Saskatchewan", + "America/Managua|Canada/Saskatchewan", + "America/Manaus|America/Boa_Vista", + "America/Manaus|America/Porto_Velho", + "America/Manaus|Brazil/West", + "America/Mexico_City|America/Merida", + "America/Mexico_City|America/Monterrey", + "America/Mexico_City|Mexico/General", + "America/New_York|America/Detroit", + "America/New_York|America/Fort_Wayne", + "America/New_York|America/Indiana/Indianapolis", + "America/New_York|America/Indiana/Marengo", + "America/New_York|America/Indiana/Petersburg", + "America/New_York|America/Indiana/Vevay", + "America/New_York|America/Indiana/Vincennes", + "America/New_York|America/Indiana/Winamac", + "America/New_York|America/Indianapolis", + "America/New_York|America/Iqaluit", + "America/New_York|America/Kentucky/Louisville", + "America/New_York|America/Kentucky/Monticello", + "America/New_York|America/Louisville", + "America/New_York|America/Montreal", + "America/New_York|America/Nassau", + "America/New_York|America/Nipigon", + "America/New_York|America/Pangnirtung", + "America/New_York|America/Thunder_Bay", + "America/New_York|America/Toronto", + "America/New_York|Canada/Eastern", + "America/New_York|EST5EDT", + "America/New_York|US/East-Indiana", + "America/New_York|US/Eastern", + "America/New_York|US/Michigan", + "America/Noronha|Brazil/DeNoronha", + "America/Panama|America/Atikokan", + "America/Panama|America/Cayman", + "America/Panama|America/Coral_Harbour", + "America/Panama|America/Jamaica", + "America/Panama|EST", + "America/Panama|Jamaica", + "America/Phoenix|America/Creston", + "America/Phoenix|America/Dawson_Creek", + "America/Phoenix|America/Hermosillo", + "America/Phoenix|MST", + "America/Phoenix|US/Arizona", + "America/Rio_Branco|America/Eirunepe", + "America/Rio_Branco|America/Porto_Acre", + "America/Rio_Branco|Brazil/Acre", + "America/Santiago|Antarctica/Palmer", + "America/Santiago|Chile/Continental", + "America/Santo_Domingo|America/Anguilla", + "America/Santo_Domingo|America/Antigua", + "America/Santo_Domingo|America/Aruba", + "America/Santo_Domingo|America/Barbados", + "America/Santo_Domingo|America/Blanc-Sablon", + "America/Santo_Domingo|America/Curacao", + "America/Santo_Domingo|America/Dominica", + "America/Santo_Domingo|America/Grenada", + "America/Santo_Domingo|America/Guadeloupe", + "America/Santo_Domingo|America/Kralendijk", + "America/Santo_Domingo|America/Lower_Princes", + "America/Santo_Domingo|America/Marigot", + "America/Santo_Domingo|America/Martinique", + "America/Santo_Domingo|America/Montserrat", + "America/Santo_Domingo|America/Port_of_Spain", + "America/Santo_Domingo|America/Puerto_Rico", + "America/Santo_Domingo|America/St_Barthelemy", + "America/Santo_Domingo|America/St_Kitts", + "America/Santo_Domingo|America/St_Lucia", + "America/Santo_Domingo|America/St_Thomas", + "America/Santo_Domingo|America/St_Vincent", + "America/Santo_Domingo|America/Tortola", + "America/Santo_Domingo|America/Virgin", + "America/Sao_Paulo|Brazil/East", + "America/St_Johns|Canada/Newfoundland", + "Asia/Almaty|Asia/Qyzylorda", + "Asia/Aqtobe|Asia/Aqtau", + "Asia/Aqtobe|Asia/Oral", + "Asia/Ashgabat|Asia/Ashkhabad", + "Asia/Baghdad|Asia/Aden", + "Asia/Baghdad|Asia/Bahrain", + "Asia/Baghdad|Asia/Kuwait", + "Asia/Baghdad|Asia/Qatar", + "Asia/Baghdad|Asia/Riyadh", + "Asia/Bangkok|Asia/Ho_Chi_Minh", + "Asia/Bangkok|Asia/Phnom_Penh", + "Asia/Bangkok|Asia/Saigon", + "Asia/Bangkok|Asia/Vientiane", + "Asia/Dhaka|Asia/Dacca", + "Asia/Dubai|Asia/Muscat", + "Asia/Hong_Kong|Hongkong", + "Asia/Jakarta|Asia/Pontianak", + "Asia/Jerusalem|Asia/Tel_Aviv", + "Asia/Jerusalem|Israel", + "Asia/Kathmandu|Asia/Katmandu", + "Asia/Kolkata|Asia/Calcutta", + "Asia/Kolkata|Asia/Colombo", + "Asia/Kuala_Lumpur|Asia/Kuching", + "Asia/Makassar|Asia/Ujung_Pandang", + "Asia/Seoul|ROK", + "Asia/Shanghai|Asia/Chongqing", + "Asia/Shanghai|Asia/Chungking", + "Asia/Shanghai|Asia/Harbin", + "Asia/Shanghai|Asia/Macao", + "Asia/Shanghai|Asia/Macau", + "Asia/Shanghai|Asia/Taipei", + "Asia/Shanghai|PRC", + "Asia/Shanghai|ROC", + "Asia/Singapore|Singapore", + "Asia/Tashkent|Asia/Samarkand", + "Asia/Tehran|Iran", + "Asia/Thimphu|Asia/Thimbu", + "Asia/Tokyo|Japan", + "Asia/Ulaanbaatar|Asia/Ulan_Bator", + "Asia/Urumqi|Asia/Kashgar", + "Australia/Adelaide|Australia/Broken_Hill", + "Australia/Adelaide|Australia/South", + "Australia/Adelaide|Australia/Yancowinna", + "Australia/Brisbane|Australia/Lindeman", + "Australia/Brisbane|Australia/Queensland", + "Australia/Darwin|Australia/North", + "Australia/Lord_Howe|Australia/LHI", + "Australia/Perth|Australia/West", + "Australia/Sydney|Australia/ACT", + "Australia/Sydney|Australia/Canberra", + "Australia/Sydney|Australia/Currie", + "Australia/Sydney|Australia/Hobart", + "Australia/Sydney|Australia/Melbourne", + "Australia/Sydney|Australia/NSW", + "Australia/Sydney|Australia/Tasmania", + "Australia/Sydney|Australia/Victoria", + "Etc/UCT|UCT", + "Etc/UTC|Etc/Universal", + "Etc/UTC|Etc/Zulu", + "Etc/UTC|UTC", + "Etc/UTC|Universal", + "Etc/UTC|Zulu", + "Europe/Astrakhan|Europe/Ulyanovsk", + "Europe/Athens|Asia/Nicosia", + "Europe/Athens|EET", + "Europe/Athens|Europe/Bucharest", + "Europe/Athens|Europe/Helsinki", + "Europe/Athens|Europe/Kiev", + "Europe/Athens|Europe/Mariehamn", + "Europe/Athens|Europe/Nicosia", + "Europe/Athens|Europe/Riga", + "Europe/Athens|Europe/Sofia", + "Europe/Athens|Europe/Tallinn", + "Europe/Athens|Europe/Uzhgorod", + "Europe/Athens|Europe/Vilnius", + "Europe/Athens|Europe/Zaporozhye", + "Europe/Chisinau|Europe/Tiraspol", + "Europe/Dublin|Eire", + "Europe/Istanbul|Asia/Istanbul", + "Europe/Istanbul|Turkey", + "Europe/Lisbon|Atlantic/Canary", + "Europe/Lisbon|Atlantic/Faeroe", + "Europe/Lisbon|Atlantic/Faroe", + "Europe/Lisbon|Atlantic/Madeira", + "Europe/Lisbon|Portugal", + "Europe/Lisbon|WET", + "Europe/London|Europe/Belfast", + "Europe/London|Europe/Guernsey", + "Europe/London|Europe/Isle_of_Man", + "Europe/London|Europe/Jersey", + "Europe/London|GB", + "Europe/London|GB-Eire", + "Europe/Moscow|Europe/Volgograd", + "Europe/Moscow|W-SU", + "Europe/Paris|Africa/Ceuta", + "Europe/Paris|Arctic/Longyearbyen", + "Europe/Paris|Atlantic/Jan_Mayen", + "Europe/Paris|CET", + "Europe/Paris|Europe/Amsterdam", + "Europe/Paris|Europe/Andorra", + "Europe/Paris|Europe/Belgrade", + "Europe/Paris|Europe/Berlin", + "Europe/Paris|Europe/Bratislava", + "Europe/Paris|Europe/Brussels", + "Europe/Paris|Europe/Budapest", + "Europe/Paris|Europe/Busingen", + "Europe/Paris|Europe/Copenhagen", + "Europe/Paris|Europe/Gibraltar", + "Europe/Paris|Europe/Ljubljana", + "Europe/Paris|Europe/Luxembourg", + "Europe/Paris|Europe/Madrid", + "Europe/Paris|Europe/Malta", + "Europe/Paris|Europe/Monaco", + "Europe/Paris|Europe/Oslo", + "Europe/Paris|Europe/Podgorica", + "Europe/Paris|Europe/Prague", + "Europe/Paris|Europe/Rome", + "Europe/Paris|Europe/San_Marino", + "Europe/Paris|Europe/Sarajevo", + "Europe/Paris|Europe/Skopje", + "Europe/Paris|Europe/Stockholm", + "Europe/Paris|Europe/Tirane", + "Europe/Paris|Europe/Vaduz", + "Europe/Paris|Europe/Vatican", + "Europe/Paris|Europe/Vienna", + "Europe/Paris|Europe/Warsaw", + "Europe/Paris|Europe/Zagreb", + "Europe/Paris|Europe/Zurich", + "Europe/Paris|Poland", + "Pacific/Auckland|Antarctica/McMurdo", + "Pacific/Auckland|Antarctica/South_Pole", + "Pacific/Auckland|NZ", + "Pacific/Chatham|NZ-CHAT", + "Pacific/Chuuk|Pacific/Truk", + "Pacific/Chuuk|Pacific/Yap", + "Pacific/Easter|Chile/EasterIsland", + "Pacific/Guam|Pacific/Saipan", + "Pacific/Honolulu|HST", + "Pacific/Honolulu|Pacific/Johnston", + "Pacific/Honolulu|US/Hawaii", + "Pacific/Majuro|Kwajalein", + "Pacific/Majuro|Pacific/Kwajalein", + "Pacific/Pago_Pago|Pacific/Midway", + "Pacific/Pago_Pago|Pacific/Samoa", + "Pacific/Pago_Pago|US/Samoa", + "Pacific/Pohnpei|Pacific/Ponape" + ] + }); + + + return moment; +})); From b380984a1e00ff8763515b91df61e2683f231743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Wed, 24 Aug 2016 02:05:05 +0200 Subject: [PATCH 106/192] Reprise de la vue historique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout de paramètres à cette vue - `opegroups` contient maintenant une liste de groupes d'opérations et non plus un dictionnaire contenant `opegroup.pk` => `opegroup` --- kfet/templates/kfet/kpsul.html | 13 ++++-- kfet/urls.py | 10 +++- kfet/views.py | 85 ++++++++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 19fca28f..58a006f5 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1083,14 +1083,19 @@ $(document).ready(function() { } function getHistory() { + var data = { + from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), + }; + console.log(data); $.ajax({ dataType: "json", - url : "{% url 'kfet.kpsul.history' %}", - method : "GET", + url : "{% url 'kfet.history.json' %}", + method : "POST", + data : data, }) .done(function(data) { - for (var opegroup_id in data) { - addOpeGroup(data[opegroup_id]); + for (var i=0; i Date: Wed, 24 Aug 2016 19:52:07 +0200 Subject: [PATCH 107/192] Page Historique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reprise du JS d'historique dans `kfet/static/kfet/js/history.js` - Adapatation de K-Psul pour l'utiliser - Création page historique avec filtres (dates, caisses, comptes) --- kfet/forms.py | 5 +- kfet/static/kfet/js/history.js | 108 ++++++++++++++++++ kfet/static/kfet/js/kfet.js | 31 ++++++ kfet/templates/kfet/account.html | 2 +- kfet/templates/kfet/base_nav.html | 1 + kfet/templates/kfet/history.html | 140 +++++++++++++++++++++++ kfet/templates/kfet/kpsul.html | 178 +++++------------------------- kfet/urls.py | 2 + kfet/views.py | 14 ++- 9 files changed, 328 insertions(+), 153 deletions(-) create mode 100644 kfet/static/kfet/js/history.js create mode 100644 kfet/templates/kfet/history.html diff --git a/kfet/forms.py b/kfet/forms.py index e39ff9e1..8302067a 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -3,7 +3,6 @@ from django import forms from django.core.exceptions import ValidationError from django.contrib.auth.models import User, Group, Permission from django.contrib.contenttypes.models import ContentType -from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms import modelformset_factory from django.utils import timezone from kfet.models import (Account, Checkout, Article, OperationGroup, Operation, @@ -343,3 +342,7 @@ class SettingsForm(forms.ModelForm): self.cleaned_data['value_account'] = None cache.delete(name) super(SettingsForm, self).clean() + +class FilterHistoryForm(forms.Form): + checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all()) + accounts = forms.ModelMultipleChoiceField(queryset = Account.objects.all()) diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js new file mode 100644 index 00000000..220db669 --- /dev/null +++ b/kfet/static/kfet/js/history.js @@ -0,0 +1,108 @@ +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
      ', + template_opegroup: '
      ', + template_ope: '
      ', + display_trigramme: true, +} diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 151a7bd5..7c53b1e7 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -8,4 +8,35 @@ $(document).ready(function() { $('.col-content-right').removeClass('col-sm-offset-4 col-md-offset-3'); } }); + + // Retrieving csrf token + var 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); +} diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html index 7983d3a6..b0123020 100644 --- a/kfet/templates/kfet/account.html +++ b/kfet/templates/kfet/account.html @@ -8,7 +8,7 @@
      -
      +
      {{ accounts|length|add:-1 }}
      compte{{ accounts|length|add:-1|pluralize }}
      diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index b4169824..8b30f0f3 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -34,6 +34,7 @@
    • Comptes
    • Caisses
    • Articles
    • +
    • Historique
    • {% if user.username != 'kfet_genericteam' %}
    • Connexion standard
    • {% endif %} diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html new file mode 100644 index 00000000..b1f50422 --- /dev/null +++ b/kfet/templates/kfet/history.html @@ -0,0 +1,140 @@ +{% extends 'kfet/base.html' %} +{% load staticfiles %} +{% load l10n %} + +{% block extra_head %} + + + + + + + + + + + +{% endblock %} + +{% block title %}Historique{% endblock %} +{% block content-header-title %}Historique{% endblock %} + +{% block content %} + +
      +
      +
      +
      +
      +
      opérations
      +
      +

      Filtres

      +
      De
      +
      à
      +
      Caisses {{ filter_form.checkouts }}
      +
      Comtpes {{ filter_form.accounts }}
      +
      +
      +
      +
      +
      + {% include 'kfet/base_messages.html' %} +
      +
      +

      Général

      +
      +
      +
      +
      +

      Opérations

      + +
      +
      +
      +
      +
      + + + +{% endblock %} diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 58a006f5..078ecfe5 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -11,6 +11,8 @@ + + {% endblock %} {% block title %}K-Psul{% endblock %} @@ -19,8 +21,6 @@ {% block content %} -{% csrf_token %} -
      @@ -98,6 +98,9 @@
      +
      +
      + @@ -111,29 +114,9 @@ $(document).ready(function() { // General // ----- - // Retrieving csrf token - var 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 amountToUKF(amount, is_cof=false) { - var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1; - return Math.round(amount * coef_cof * 10); - } - // Retrieve settings - var settings = {} + settings = {} function resetSettings() { $.ajax({ @@ -979,114 +962,12 @@ $(document).ready(function() { // History // ----- - var history_container = $('#history'); - var history_operationgroup_html = '
      '; - var history_operation_html = '
      '; - - function getOpeHtml(ope, is_cof=false, trigramme='') { - var ope_html = $(history_operation_html); - var amount = parseFloat(ope['amount']); - var amountUKF = amountToUKF(amount, is_cof); - var infos1 = '', infos2 = ''; - - if (ope['type'] == 'deposit') { - infos1 = amount.toFixed(2)+'€'; - infos2 = 'Charge'; - } else if (ope['type'] == 'withdraw') { - infos1 = amount.toFixed(2)+'€'; - infos2 = 'Retrait'; - } else { - infos1 = ope['article_nb']; - infos2 = ope['article__name']; - } - - if (trigramme == 'LIQ') - amountUKF = (- amount).toFixed(2) +"€"; - - ope_html - .attr('data-ope', ope['id']) - .find('.amount').text(amountUKF).end() - .find('.infos1').text(infos1).end() - .find('.infos2').text(infos2).end(); - - if (ope['addcost_for__trigramme']) { - ope_html.find('.addcost').text('('+amountToUKF(ope['addcost_amount'],is_cof)+'UKF pour '+ope['addcost_for__trigramme']+')') - } - - if (ope['canceled_at']) { - ope_html.addClass('canceled'); - var cancel = 'Annulé'; - if (ope['canceled_by__trigramme']) - cancel += ' par '+ope['canceled_by__trigramme']; - var canceled_at = moment.tz(ope['canceled_at'],'UTC'); - cancel += ' le '+canceled_at.tz('Europe/Paris').format('DD/MM/YY à HH:mm:ss'); - ope_html.find('.canceled').text(cancel); - } - - return ope_html; - } - - function getOpegroupHtml(opegroup) { - var opegroup_html = $(history_operationgroup_html); - var at = moment.tz(opegroup['at'], 'UTC'); - var at_formated = at.tz('Europe/Paris').format('HH:mm:ss'); - var amount = parseFloat(opegroup['amount']); - var trigramme = opegroup['on_acc__trigramme']; - if (opegroup['on_acc__trigramme'] == 'LIQ') { - var amount = (- amount).toFixed(2) +'€'; - } else { - var amount = amountToUKF(amount, opegroup['is_cof']); - } - var valid_by = ''; - if (opegroup['valid_by__trigramme']) - valid_by = 'Par '+opegroup['valid_by__trigramme']; - var comment = opegroup['comment'] || ''; - - opegroup_html - .attr('data-opegroup', opegroup['id']) - .find('.time').text(at_formated).end() - .find('.amount').text(amount).end() - .find('.trigramme').text(trigramme).end() - .find('.valid_by').text(valid_by).end() - .find('.comment').text(comment).end(); - - return opegroup_html; - } - - function addOpeGroup(opegroup) { - checkOrCreateDay(opegroup['at']); - var $opegroup_html = getOpegroupHtml(opegroup); - history_container.find('.day').first().after($opegroup_html); - - // Ajout des opérations de ce groupe - var $opegroup = history_container.find('[data-opegroup='+opegroup['id']+']'); - for (var i=0; i < opegroup['opes'].length; i++) { - var $ope = getOpeHtml(opegroup['opes'][i], opegroup['is_cof'], opegroup['on_acc__trigramme']); - $ope.attr('data-opegroup', opegroup['id']); - $opegroup.after($ope); - } - } - - var history_day_default_html = '
      '; - - function checkOrCreateDay(at) { - var at = moment.tz(at, 'UTC').tz('Europe/Paris'); - var mostRecentDay = history_container.find('.day').first(); - if (mostRecentDay.length == 0 || at.month() != mostRecentDay.attr('data-month') || at.date() != mostRecentDay.attr('data-day')) { - var day_html = $(history_day_default_html); - day_html - .attr('data-month', at.month()) - .attr('data-day', at.date()) - .text(at.format('D MMMM')); - history_container.prepend(day_html); - } - } + khistory = new KHistory(); function getHistory() { var data = { from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), }; - console.log(data); $.ajax({ dataType: "json", url : "{% url 'kfet.history.json' %}", @@ -1095,15 +976,11 @@ $(document).ready(function() { }) .done(function(data) { for (var i=0; i 0) cancelOperations(opes_to_cancel); @@ -1206,24 +1085,25 @@ $(document).ready(function() { }); function cancelOpeGroup(opegroup) { - var opegroup_html = history_container.find('.opegroup[data-opegroup='+opegroup['id']+']'); - if (opegroup_html.find('.trigramme').text() == 'LIQ') { - var amount = (- parseFloat(opegroup['amount']).toFixed(2))+'€'; - } else { - var amount = amountToUKF(opegroup['amount'], opegroup['on_acc__is_cof']); - } - opegroup_html.find('.amount').text(amount); + var $opegroup = khistory.$container.find('.opegroup').filter(function() { + return $(this).data('opegroup') == opegroup['id'] + }); + var tri = $opegroup.find('.trigramme').text(); + var amount = amountDisplay( + parseFloat(opegroup['amount'], opegroup['is_cof'], tri)) + $opegroup.find('.amount').text(amount); } function cancelOpe(ope) { - var ope_html = history_container.find('[data-ope='+ope['id']+']'); - ope_html.addClass('canceled'); + var $ope = khistory.$container.find('.ope').filter(function() { + return $(this).data('ope') == ope['id'] + }); var cancel = 'Annulé'; + var canceled_at = dateUTCToParis(ope['canceled_at']); if (ope['canceled_by__trigramme']) cancel += ' par '+ope['canceled_by__trigramme']; - var canceled_at = moment.tz(ope['canceled_at'], 'UTC'); - cancel += ' le '+canceled_at.tz('Europe/Paris').format('DD/MM/YY à HH:mm:ss'); - ope_html.find('.canceled').text(cancel); + cancel += ' le '+canceled_at.format('DD/MM/YY à HH:mm:ss'); + $ope.addClass('canceled').find('.canceled').text(cancel); } // ----- @@ -1285,7 +1165,7 @@ $(document).ready(function() { coolReset(give_tri_focus); resetCheckout(); resetArticles(); - resetHistory(); + khistory.reset(); resetSettings(); getArticles(); getHistory(); diff --git a/kfet/urls.py b/kfet/urls.py index 330adca9..51e6c785 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -8,6 +8,8 @@ urlpatterns = [ name = 'kfet.home'), url(r'^login/genericteam$', views.login_genericteam, name = 'kfet.login.genericteam'), + url(r'^history$', views.history, + name = 'kfet.history'), # ----- # Account urls diff --git a/kfet/views.py b/kfet/views.py index 25fdfae8..4b9a6553 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -975,13 +975,13 @@ def kpsul_cancel_operations(request): # Need refresh from db cause we used update on querysets opegroups_pk = [ opegroup.pk for opegroup in to_groups_amounts ] opegroups = (OperationGroup.objects - .values('id','amount','on_acc__cofprofile__is_cof').filter(pk__in=opegroups_pk)) + .values('id','amount','is_cof').filter(pk__in=opegroups_pk)) for opegroup in opegroups: websocket_data['opegroups'].append({ 'cancellation': True, 'id': opegroup['id'], 'amount': opegroup['amount'], - 'on_acc__is_cof': opegroup['on_acc__cofprofile__is_cof'], + 'is_cof': opegroup['is_cof'], }) canceled_by__trigramme = canceled_by and canceled_by.trigramme or None for ope in opes: @@ -1088,6 +1088,16 @@ def kpsul_articles_data(request): .filter(is_sold=True)) return JsonResponse({ 'articles': list(articles) }) +@permission_required('kfet.is_team') +def history(request): + data = { + 'filter_form': FilterHistoryForm(), + 'settings': { + 'subvention_cof': Settings.SUBVENTION_COF(), + } + } + return render(request, 'kfet/history.html', data) + # ----- # Settings views # ----- From c4fa4ea20cf62187f90556bab55b778e94fa3046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Wed, 24 Aug 2016 23:34:14 +0200 Subject: [PATCH 108/192] Historique d'un compte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reprise en utilisant `history.js` - Fix csrf_token sur ajax POST K-Psul - Fix annulation K-Psul - Ajouts de select_related pour économiser de la requête BDD --- kfet/static/kfet/js/history.js | 7 +- kfet/static/kfet/js/kfet.js | 2 +- kfet/templates/kfet/account_read.html | 100 +++++++++++--------------- kfet/templates/kfet/kpsul.html | 2 + kfet/views.py | 21 +++--- 5 files changed, 61 insertions(+), 71 deletions(-) diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index 220db669..9d35e5eb 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -74,10 +74,11 @@ function KHistory(options={}) { .data('opegroup', opegroup['id']) .find('.time').text(at).end() .find('.amount').text(amount).end() - .find('.comment').text(comment).end(); + .find('.comment').text(comment).end() + .find('.trigramme').text(trigramme).end(); - if (this.display_trigramme) - $opegroup_html.find('.trigramme').text(trigramme); + 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']); diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 7c53b1e7..719fb3d8 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -10,7 +10,7 @@ $(document).ready(function() { }); // Retrieving csrf token - var csrftoken = Cookies.get('csrftoken'); + csrftoken = Cookies.get('csrftoken'); // Appending csrf token to ajax post requests function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index 8341becf..e8931099 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -1,5 +1,16 @@ {% extends "kfet/base.html" %} +{% load staticfiles %} {% load kfet_tags %} +{% load l10n %} + +{% block extra_head %} + + + + + + +{% endblock %} {% block title %} {% if account.user == request.user %} @@ -43,67 +54,42 @@

      Historique

      - {% spaceless %} - {% for ope in history %} - {% ifchanged ope.group.at|date:'dmY' %} -
      - {{ ope.group.at|date:'l j F' }} -
      - {% endifchanged %} - {% ifchanged ope.group.pk %} -
      - {{ ope.group.at|date:'H:i:s' }} - - {% if ope.group.on_acc.trigramme == 'LIQ' %} - {{ ope.group.amount|floatformat:2 }} - {% else %} - {{ ope.group.amount|ukf:ope.group.is_cof }} - {% endif %} - - {% if perms.kfet.is_team and ope.group.valid_by %} - Par {{ ope.group.valid_by.trigramme }} - {% endif %} - {% if ope.group.comment %} - {{ ope.group.comment }} - {% endif %} -
      - {% endifchanged %} -
      - {% if ope.group.on_acc.trigramme == 'LIQ' %} - {{ ope.amount|floatformat:2 }}€ - {% else %} - {{ ope.amount|ukf:ope.group.is_cof }} - {% endif %} - {% if ope.type == "purchase" %} - {{ ope.article_nb }} - {{ ope.article.name }} - {% else %} - {{ ope.amount|floatformat:2 }}€ - - {% if ope.type == "deposit" %}Charge{% else %}Retrait{% endif %} - - {% endif %} - {% if ope.addcost_for %} - - {{ ope.addcost_amount|ukf:ope.group.is_cof }}UKF pour {{ ope.addcost_for.trigramme }} - - {% endif %} - {% if ope.canceled_at %} - {% if perms.kfet.is_team and ope.canceled_by %} - - Annulé par {{ ope.canceled_by.trigramme }} le {{ ope.canceled_at }} - - {% else %} - Annulé le {{ ope.canceled_at }} - {% endif %} - {% endif %} -
      - {% endfor %} - {% endspaceless %}
      + + {% endblock %} diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 078ecfe5..7bf27747 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -108,6 +108,8 @@ {{ operation_formset.empty_form }}
      +{% csrf_token %} + + @@ -133,6 +135,120 @@ $(document).ready(function() { getHistory(); }); + khistory.$container.selectable({ + filter: 'div.opegroup, div.ope', + selected: function(e, ui) { + $(ui.selected).each(function() { + if ($(this).hasClass('opegroup')) { + var opegroup = $(this).data('opegroup'); + $(this).siblings('.ope').filter(function() { + return $(this).data('opegroup') == opegroup + }).addClass('ui-selected'); + } + }); + }, + }); + + $(document).on('keydown', function (e) { + if (e.keyCode == 46) { + // DEL (Suppr) + var opes_to_cancel = []; + khistory.$container.find('.ope.ui-selected').each(function () { + opes_to_cancel.push($(this).data('ope')); + }); + if (opes_to_cancel.length > 0) + confirmCancel(opes_to_cancel); + } + }); + + function confirmCancel(opes_to_cancel) { + var nb = opes_to_cancel.length; + var content = nb+" opérations vont être annulées"; + $.confirm({ + title: 'Confirmation', + content: content, + backgroundDismiss: true, + animation: 'top', + closeAnimation: 'bottom', + keyboardEnabled: true, + confirm: function() { + cancelOperations(opes_to_cancel); + } + }); + } + + function requestAuth(data, callback) { + var content = getErrorsHtml(data); + content += '', + $.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(); + }); + }, + }); + } + + function getErrorsHtml(data) { + var content = ''; + if ('missing_perms' in data['errors']) { + content += 'Permissions manquantes'; + content += '
        '; + for (var i=0; i'; + content += '
      '; + } + if ('negative' in data['errors']) + content += 'Autorisation de négatif requise'; + return content; + } + + function cancelOperations(opes_array, password = '') { + var data = { 'operations' : opes_array } + $.ajax({ + dataType: "json", + url : "{% url 'kfet.kpsul.cancel_operations' %}", + method : "POST", + data : data, + beforeSend: function ($xhr) { + $xhr.setRequestHeader("X-CSRFToken", csrftoken); + if (password != '') + $xhr.setRequestHeader("KFetPassword", password); + }, + + }) + .done(function(data) { + khistory.$container.find('.ui-selected').removeClass('ui-selected'); + }) + .fail(function($xhr) { + var data = $xhr.responseJSON; + switch ($xhr.status) { + case 403: + requestAuth(data, function(password) { + cancelOperations(opes_array, password); + }); + break; + case 400: + displayErrors(getErrorsHtml(data)); + break; + } + + }); + } + + getHistory(); }); diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index c5153f78..2b64c8d5 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1065,11 +1065,12 @@ $(document).ready(function() { filter: 'div.opegroup, div.ope', selected: function(e, ui) { $(ui.selected).each(function() { - if ($(this).hasClass('opegroup')) + if ($(this).hasClass('opegroup')) { var opegroup = $(this).data('opegroup'); $(this).siblings('.ope').filter(function() { return $(this).data('opegroup') == opegroup }).addClass('ui-selected'); + } }); }, }); From 27b0e3737d53df2a167baf5202a6fe8d4d121373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Fri, 26 Aug 2016 15:30:40 +0200 Subject: [PATCH 111/192] Ajout faire des transferts --- kfet/forms.py | 42 +++++++- kfet/models.py | 33 ++++-- kfet/static/kfet/css/transfers_form.css | 58 ++++++++++ kfet/static/kfet/js/kfet.js | 94 ++++++++++++++--- kfet/templates/kfet/kpsul.html | 75 ++----------- kfet/templates/kfet/transfers.html | 16 +++ kfet/templates/kfet/transfers_create.html | 122 ++++++++++++++++++++++ kfet/urls.py | 22 +++- kfet/views.py | 86 +++++++++++++-- 9 files changed, 445 insertions(+), 103 deletions(-) create mode 100644 kfet/static/kfet/css/transfers_form.css create mode 100644 kfet/templates/kfet/transfers.html create mode 100644 kfet/templates/kfet/transfers_create.html diff --git a/kfet/forms.py b/kfet/forms.py index 8302067a..6709beb1 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -3,10 +3,12 @@ from django import forms from django.core.exceptions import ValidationError from django.contrib.auth.models import User, Group, Permission from django.contrib.contenttypes.models import ContentType -from django.forms import modelformset_factory +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) + CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer, + TransferGroup) from gestioncof.models import CofProfile # ----- @@ -346,3 +348,39 @@ class SettingsForm(forms.ModelForm): 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, +) diff --git a/kfet/models.py b/kfet/models.py index a2d50cdc..51560d4b 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -101,12 +101,9 @@ class Account(models.Model): data['is_free'] = True return data - def perms_to_perform_operation(self, amount, overdraft_duration_max=None, \ - overdraft_amount_max=None): - if overdraft_duration_max is None: - overdraft_duration_max = Settings.OVERDRAFT_DURATION() - if overdraft_amount_max is None: - overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() + 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 @@ -572,17 +569,27 @@ class Settings(models.Model): @staticmethod def OVERDRAFT_DURATION(): + overdraft_duration = cache.get('OVERDRAFT_DURATION') + if overdraft_duration: + return overdraft_duration try: - return Settings.setting_inst("OVERDRAFT_DURATION").value_duration + overdraft_duration = Settings.setting_inst("OVERDRAFT_DURATION").value_duration except Settings.DoesNotExist: - return timedelta() + 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: - return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal + overdraft_amount = Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal except Settings.DoesNotExist: - return 0 + overdraft_amount = 0 + cache.set('OVERDRAFT_AMOUNT', overdraft_amount) + return overdraft_amount def CANCEL_DURATION(): try: @@ -614,5 +621,11 @@ class Settings(models.Model): s.value_duration = timedelta(minutes=5) # 5min s.save() + @staticmethod + def empty_cache(): + cache.delete_many([ + 'SUBVENTION_COF','OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT', + ]) + class GenericTeamToken(models.Model): token = models.CharField(max_length = 50, unique = True) diff --git a/kfet/static/kfet/css/transfers_form.css b/kfet/static/kfet/css/transfers_form.css new file mode 100644 index 00000000..f222e490 --- /dev/null +++ b/kfet/static/kfet/css/transfers_form.css @@ -0,0 +1,58 @@ +.transfer_formset { + background:#FFF; +} + +.transfer_formset thead { + height:40px; + background:#c8102e; + color:#fff; + font-size:20px; + font-weight:bold; + text-align:center; +} + +.transfer_form { + height:50px; +} + +.transfer_form td { + 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; + text-transform:uppercase; +} + +.transfer_form .from_acc_data, .transfer_form .to_acc_data { + width:30%; + text-align:center; + vertical-align:middle; + 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%; +} diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 719fb3d8..1bdeef8e 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -9,21 +9,22 @@ $(document).ready(function() { } }); - // 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); - } + 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) { @@ -40,3 +41,68 @@ 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 += '
        '; + if (data['errors']['operation_group'].indexOf('on_acc') != -1) + content += '
      • Pas de compte sélectionné
      • '; + if (data['errors']['operation_group'].indexOf('checkout') != -1) + content += '
      • Pas de caisse sélectionnée
      • '; + content += '
      '; + } + if ('missing_perms' in data['errors']) { + content += 'Permissions manquantes'; + content += '
        '; + for (var i=0; i'; + content += '
      '; + } + if ('negative' in data['errors']) + content += 'Autorisation de négatif requise'; + if ('addcost' in data['errors']) { + content += '
        '; + if (data['errors']['addcost'].indexOf('__all__') != -1) + content += '
      • Compte invalide
      • '; + if (data['errors']['addcost'].indexOf('amount') != -1) + content += '
      • Montant invalide
      • '; + content += '
      '; + } + return content; +} + +function requestAuth(data, callback, focus_next = null) { + var content = getErrorsHtml(data); + content += '', + $.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; + } + }); +} + diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 2b64c8d5..eed7fc6d 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -141,9 +141,8 @@ $(document).ready(function() { // Initializing var account_container = $('#account'); - var triInput = $('#id_trigramme') - var triPattern = /^[^a-z]{3}$/ - var account_data = {} + var triInput = $('#id_trigramme'); + var account_data = {}; var account_data_default = { 'id' : 0, 'name' : '', @@ -155,7 +154,7 @@ $(document).ready(function() { 'is_frozen' : false, 'departement': '', 'nickname' : '', - } + }; // Display data function displayAccountData() { @@ -212,7 +211,7 @@ $(document).ready(function() { function retrieveAccountData(tri) { $.ajax({ dataType: "json", - url : "{% url 'kfet.kpsul.account_data' %}", + url : "{% url 'kfet.account.read.json' %}", method : "POST", data : { trigramme: tri }, }) @@ -229,7 +228,7 @@ $(document).ready(function() { triInput.on('input', function() { var tri = triInput.val().toUpperCase(); // Checking if tri is valid to avoid sending requests - if (tri.match(triPattern)) { + if (isValidTrigramme(tri)) { retrieveAccountData(tri); } else { resetAccountData(); @@ -318,31 +317,6 @@ $(document).ready(function() { // Auth // ----- - function requestAuth(data, callback) { - var content = getErrorsHtml(data); - content += '', - $.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() { this._lastFocused = articleSelect; } - }); - } - function askComment(callback) { var comment = $('#id_comment').val(); $.confirm({ @@ -374,38 +348,7 @@ $(document).ready(function() { // ----- // Errors ajax // ----- - - function getErrorsHtml(data) { - var content = ''; - if ('operation_group' in data['errors']) { - content += 'Général'; - content += '
        '; - if (data['errors']['operation_group'].indexOf('on_acc') != -1) - content += '
      • Pas de compte sélectionné
      • '; - if (data['errors']['operation_group'].indexOf('checkout') != -1) - content += '
      • Pas de caisse sélectionnée
      • '; - content += '
      '; - } - if ('missing_perms' in data['errors']) { - content += 'Permissions manquantes'; - content += '
        '; - for (var i=0; i'; - content += '
      '; - } - if ('negative' in data['errors']) - content += 'Autorisation de négatif requise'; - if ('addcost' in data['errors']) { - content += '
        '; - if (data['errors']['addcost'].indexOf('__all__') != -1) - content += '
      • Compte invalide
      • '; - if (data['errors']['addcost'].indexOf('amount') != -1) - content += '
      • Montant invalide
      • '; - content += '
      '; - } - return content; - } - +x function displayErrors(html) { $.alert({ title: 'Erreurs', @@ -445,7 +388,7 @@ $(document).ready(function() { var data = $xhr.responseJSON; switch ($xhr.status) { case 403: - requestAuth(data, performOperations); + requestAuth(data, performOperations, articleSelect); break; case 400: if ('need_comment' in data['errors']) { @@ -493,7 +436,7 @@ $(document).ready(function() { case 403: requestAuth(data, function(password) { cancelOperations(opes_array, password); - }); + }, triInput); break; case 400: displayErrors(getErrorsHtml(data)); @@ -1023,7 +966,7 @@ $(document).ready(function() { case 403: requestAuth(data, function(password) { sendAddcost(trigramme, amount, password); - }); + }, triInput); break; case 400: askAddcost(getErrorsHtml(data)); diff --git a/kfet/templates/kfet/transfers.html b/kfet/templates/kfet/transfers.html new file mode 100644 index 00000000..4ed58465 --- /dev/null +++ b/kfet/templates/kfet/transfers.html @@ -0,0 +1,16 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Transferts{% endblock %} +{% block content-header-title %}Transferts{% endblock %} + +{% block content %} + +
      +
      + {% csrf_token %} + {{ form }} + +
      +
      + +{% endblock %} diff --git a/kfet/templates/kfet/transfers_create.html b/kfet/templates/kfet/transfers_create.html new file mode 100644 index 00000000..a28ab892 --- /dev/null +++ b/kfet/templates/kfet/transfers_create.html @@ -0,0 +1,122 @@ +{% extends 'kfet/base.html' %} +{% load staticfiles %} + +{% block extra_head %} + + +{% endblock %} + +{% block title %}Nouveaux transferts{% endblock %} +{% block content-header-title %}Nouveaux transferts{% endblock %} + +{% block content %} + +{% csrf_token %} + +
      + + + + + + + + + + + + {% for form in transfer_formset %} + + + + + + + + {% endfor %} + +
      DeVers
      + + {{ form.from_acc }} + {{ form.amount }} + + {{ form.to_acc }} +
      +
      + + + {{ transfer_formset.management_form }} + +
      +
      + + + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 51e6c785..ef8f731a 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -117,8 +117,6 @@ urlpatterns = [ # ----- url('^k-psul/$', views.kpsul, name = 'kfet.kpsul'), - url('^k-psul/account_data$', views.kpsul_account_data, - name = 'kfet.kpsul.account_data'), url('^k-psul/checkout_data$', views.kpsul_checkout_data, name = 'kfet.kpsul.checkout_data'), url('^k-psul/perform_operations$', views.kpsul_perform_operations, @@ -136,17 +134,31 @@ urlpatterns = [ # JSON urls # ----- - url('^history.json$', views.history_json, + url(r'^history.json$', views.history_json, name = 'kfet.history.json'), + url(r'^accounts/read.json$', views.account_read_json, + name = 'kfet.account.read.json'), + # ----- # Settings urls # ----- - url('^settings/$', + url(r'^settings/$', permission_required('kfet.change_settings')(views.SettingsList.as_view()), name = 'kfet.settings'), - url('^settings/(?P\d+)/edit$', + url(r'^settings/(?P\d+)/edit$', permission_required('kfet.change_settings')(views.SettingsUpdate.as_view()), name = 'kfet.settings.update'), + + # ----- + # Transfers urls + # ----- + + url(r'^transfers/$', views.home, + name = 'kfet.transfers'), + url(r'^transfers/new$', views.transfer_create, + name = 'kfet.transfers.create'), + url(r'^transfers/perform$', views.perform_transfers, + name = 'kfet.transfers.perform'), ] diff --git a/kfet/views.py b/kfet/views.py index 1596fc44..65199277 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -600,7 +600,7 @@ def kpsul_get_settings(request): return JsonResponse(data) @permission_required('kfet.is_team') -def kpsul_account_data(request): +def account_read_json(request): trigramme = request.POST.get('trigramme', '') account = get_object_or_404(Account, trigramme=trigramme) data = { 'id': account.pk, 'name': account.name, 'email': account.email, @@ -931,13 +931,9 @@ def kpsul_cancel_operations(request): return JsonResponse(data) # Checking permissions or stop - overdraft_duration_max = Settings.OVERDRAFT_DURATION() - overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() for account in to_accounts_balances: (perms, stop) = account.perms_to_perform_operation( - amount = to_accounts_balances[account], - overdraft_duration_max = overdraft_duration_max, - overdraft_amount_max = overdraft_amount_max) + amount = to_accounts_balances[account]) required_perms |= perms stop_all = stop_all or stop @@ -1125,5 +1121,83 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView): form.add_error(None, 'Permission refusée') return self.form_invalid(form) # Creating + Settings.empty_cache() return super(SettingsUpdate, self).form_valid(form) +# ----- +# Transfer views +# ----- + +def transfer_create(request): + transfer_formset = TransferFormSet(queryset=Transfer.objects.none()) + return render(request, 'kfet/transfers_create.html', + { 'transfer_formset': transfer_formset }) + +def perform_transfers(request): + data = { 'errors': {}, 'transfers': [], 'transfergroup': 0 } + + # Checking transfer_formset + transfer_formset = TransferFormSet(request.POST) + if not transfer_formset.is_valid(): + return JsonResponse({ 'errors': list(transfer_formset.errors)}, status=400) + + transfers = transfer_formset.save(commit = False) + + # Initializing vars + required_perms = set() # Required perms to perform all transfers + to_accounts_balances = defaultdict(lambda:0) # For balances of accounts + + for transfer in transfers: + to_accounts_balances[transfer.from_acc] -= transfer.amount + to_accounts_balances[transfer.to_acc] += transfer.amount + + stop_all = False + + # Checking if ok on all accounts + for account in to_accounts_balances: + (perms, stop) = account.perms_to_perform_operation( + amount = to_accounts_balances[account]) + required_perms |= perms + stop_all = stop_all or stop + + if stop_all or not request.user.has_perms(required_perms): + missing_perms = get_missing_perms(required_perms, request.user) + if missing_perms: + data['errors']['missing_perms'] = missing_perms + if stop_all: + data['errors']['negative'] = True + return JsonResponse(data, status=403) + + with transaction.atomic(): + # Updating balances accounts + for account in to_accounts_balances: + Account.objects.filter(pk=account.pk).update( + balance = F('balance') + to_accounts_balances[account]) + account.refresh_from_db() + if account.balance < 0: + if hasattr(account, 'negative'): + if not account.negative.start: + account.negative.start = timezone.now() + account.negative.save() + else: + negative = AccountNegative( + account = account, start = timezone.now()) + negative.save() + elif (hasattr(account, 'negative') + and not account.negative.balance_offset): + account.negative.delete() + + # Creating transfer group + transfergroup = TransferGroup() + if required_perms: + transfergroup.valid_by = request.user.profile.account_kfet + transfergroup.save() + data['transfergroup'] = transfergroup.pk + + # Saving all transfers with group + for transfer in transfers: + transfer.group = transfergroup + transfer.save() + data['transfers'].append(transfer.pk) + + return JsonResponse(data) From 022e1f39842d12ab8bd16d42a6ebded2d3c566f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Fri, 26 Aug 2016 15:38:44 +0200 Subject: [PATCH 112/192] Fix --- kfet/templates/kfet/kpsul.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index eed7fc6d..ff64e960 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -348,7 +348,7 @@ $(document).ready(function() { // ----- // Errors ajax // ----- -x + function displayErrors(html) { $.alert({ title: 'Erreurs', From be8243c4cecaf72084d893bbf211d868e6a61dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Fri, 26 Aug 2016 20:14:00 +0200 Subject: [PATCH 113/192] Ajout liste transferts --- kfet/static/kfet/css/transfers_form.css | 25 +++++++++++--- kfet/templates/kfet/transfers.html | 41 ++++++++++++++++++++--- kfet/templates/kfet/transfers_create.html | 13 ++++--- kfet/urls.py | 4 +-- kfet/views.py | 26 +++++++++++--- 5 files changed, 85 insertions(+), 24 deletions(-) diff --git a/kfet/static/kfet/css/transfers_form.css b/kfet/static/kfet/css/transfers_form.css index f222e490..b2920b5d 100644 --- a/kfet/static/kfet/css/transfers_form.css +++ b/kfet/static/kfet/css/transfers_form.css @@ -1,3 +1,20 @@ +.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; } @@ -11,11 +28,9 @@ text-align:center; } -.transfer_form { - height:50px; -} - .transfer_form td { + height:50px; + vertical-align:middle; padding:0 !important; } @@ -31,13 +46,13 @@ 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; - vertical-align:middle; font-size:20px; } diff --git a/kfet/templates/kfet/transfers.html b/kfet/templates/kfet/transfers.html index 4ed58465..5a32527b 100644 --- a/kfet/templates/kfet/transfers.html +++ b/kfet/templates/kfet/transfers.html @@ -6,11 +6,42 @@ {% block content %}
      -
      - {% csrf_token %} - {{ form }} - -
      +
      +
      +
      +
      + +
      +
      +
      + {% include 'kfet/base_messages.html' %} +
      +
      +

      Liste des transferts

      +
      + {% for transfergroup in transfergroups %} +
      + {{ transfergroup.at }} + {{ transfergroup.valid_by.trigramme }} + {{ transfergroup.comment }} +
      + {% for transfer in transfergroup.transfers.all %} +
      + {{ transfer.amount }} € + {{ transfer.from_acc.trigramme }} + --> + {{ transfer.to_acc.trigramme }} +
      + {% endfor %} + {% endfor %} + +
      +
      +
      {% endblock %} diff --git a/kfet/templates/kfet/transfers_create.html b/kfet/templates/kfet/transfers_create.html index a28ab892..294db1e5 100644 --- a/kfet/templates/kfet/transfers_create.html +++ b/kfet/templates/kfet/transfers_create.html @@ -14,6 +14,11 @@ {% csrf_token %}
      +
      + + {{ transfer_formset.management_form }} +
      @@ -42,12 +47,6 @@ {% endfor %}
      -
      - - - {{ transfer_formset.management_form }} - -
      - - - + + + {% endblock %} diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html index a63d088f..5b78a8b7 100644 --- a/kfet/templates/kfet/history.html +++ b/kfet/templates/kfet/history.html @@ -4,15 +4,15 @@ {% block extra_head %} - + - - - - + + + + diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 9322e9ba..956e3617 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -8,9 +8,9 @@ - - - + + + {% endblock %} @@ -613,7 +613,7 @@ $(document).ready(function() { return false; } - articleSelect.on('keydown', function(e) { + articleSelect.on('keypress', function(e) { var text = articleSelect.val(); // Comportement normal pour ces touches if (normalKeys.test(e.keyCode) || e.ctrlKey) { @@ -655,7 +655,6 @@ $(document).ready(function() { return true; } var nb = articleNb.val()+e.key; - print(nb); if (is_nb_ok(nb)) return true; return false; From 7bfd2e2f9a1b3a5a0d9e9fca8e37b3809ab402ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 3 Sep 2016 01:21:49 +0200 Subject: [PATCH 150/192] Ajout /ws/ for websocket path --- kfet/routing.py | 2 +- kfet/templates/kfet/kpsul.html | 2 +- static/bootstrap-datetimepicker.min.css | 5 - static/bootstrap-datetimepicker.min.js | 9 - static/moment-fr.js | 64 - static/moment-timezone-with-data-2010-2020.js | 1196 ----- static/moment.js | 4195 ----------------- 7 files changed, 2 insertions(+), 5471 deletions(-) delete mode 100644 static/bootstrap-datetimepicker.min.css delete mode 100644 static/bootstrap-datetimepicker.min.js delete mode 100644 static/moment-fr.js delete mode 100644 static/moment-timezone-with-data-2010-2020.js delete mode 100644 static/moment.js diff --git a/kfet/routing.py b/kfet/routing.py index f5fe4544..e7bcca55 100644 --- a/kfet/routing.py +++ b/kfet/routing.py @@ -8,7 +8,7 @@ from channels.routing import route, route_class from kfet import consumers channel_routing = [ - route_class(consumers.KPsul, path=r"^/k-fet/k-psul/$"), + route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"), #route("websocket.connect", ws_kpsul_history_connect), #route('websocket.receive', ws_message) ] diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 956e3617..fb9682dd 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1059,7 +1059,7 @@ $(document).ready(function() { websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]} - socket = new ReconnectingWebSocket("ws://" + window.location.host + "/k-fet/k-psul/"); + socket = new ReconnectingWebSocket("ws://" + window.location.host + "/ws/k-fet/k-psul/"); socket.onmessage = function(e) { data = $.extend({}, websocket_msg_default, JSON.parse(e.data)); diff --git a/static/bootstrap-datetimepicker.min.css b/static/bootstrap-datetimepicker.min.css deleted file mode 100644 index cac7b200..00000000 --- a/static/bootstrap-datetimepicker.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Datetimepicker for Bootstrap 3 - * version : 4.17.37 - * https://github.com/Eonasdan/bootstrap-datetimepicker/ - */.bootstrap-datetimepicker-widget{display:block;list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid white;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em !important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0} diff --git a/static/bootstrap-datetimepicker.min.js b/static/bootstrap-datetimepicker.min.js deleted file mode 100644 index db3d085d..00000000 --- a/static/bootstrap-datetimepicker.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! version : 4.17.37 - ========================================================= - bootstrap-datetimejs - https://github.com/Eonasdan/bootstrap-datetimepicker - Copyright (c) 2015 Jonathan Peterson - ========================================================= - */ -!function(a){"use strict";if("function"==typeof define&&define.amd)define(["jquery","moment"],a);else if("object"==typeof exports)a(require("jquery"),require("moment"));else{if("undefined"==typeof jQuery)throw"bootstrap-datetimepicker requires jQuery to be loaded first";if("undefined"==typeof moment)throw"bootstrap-datetimepicker requires Moment.js to be loaded first";a(jQuery,moment)}}(function(a,b){"use strict";if(!b)throw new Error("bootstrap-datetimepicker requires Moment.js to be loaded first");var c=function(c,d){var e,f,g,h,i,j,k,l={},m=!0,n=!1,o=!1,p=0,q=[{clsName:"days",navFnc:"M",navStep:1},{clsName:"months",navFnc:"y",navStep:1},{clsName:"years",navFnc:"y",navStep:10},{clsName:"decades",navFnc:"y",navStep:100}],r=["days","months","years","decades"],s=["top","bottom","auto"],t=["left","right","auto"],u=["default","top","bottom"],v={up:38,38:"up",down:40,40:"down",left:37,37:"left",right:39,39:"right",tab:9,9:"tab",escape:27,27:"escape",enter:13,13:"enter",pageUp:33,33:"pageUp",pageDown:34,34:"pageDown",shift:16,16:"shift",control:17,17:"control",space:32,32:"space",t:84,84:"t","delete":46,46:"delete"},w={},x=function(a){var c,e,f,g,h,i=!1;return void 0!==b.tz&&void 0!==d.timeZone&&null!==d.timeZone&&""!==d.timeZone&&(i=!0),void 0===a||null===a?c=i?b().tz(d.timeZone).startOf("d"):b().startOf("d"):i?(e=b().tz(d.timeZone).utcOffset(),f=b(a,j,d.useStrict).utcOffset(),f!==e?(g=b().tz(d.timeZone).format("Z"),h=b(a,j,d.useStrict).format("YYYY-MM-DD[T]HH:mm:ss")+g,c=b(h,j,d.useStrict).tz(d.timeZone)):c=b(a,j,d.useStrict).tz(d.timeZone)):c=b(a,j,d.useStrict),c},y=function(a){if("string"!=typeof a||a.length>1)throw new TypeError("isEnabled expects a single character string parameter");switch(a){case"y":return-1!==i.indexOf("Y");case"M":return-1!==i.indexOf("M");case"d":return-1!==i.toLowerCase().indexOf("d");case"h":case"H":return-1!==i.toLowerCase().indexOf("h");case"m":return-1!==i.indexOf("m");case"s":return-1!==i.indexOf("s");default:return!1}},z=function(){return y("h")||y("m")||y("s")},A=function(){return y("y")||y("M")||y("d")},B=function(){var b=a("").append(a("").append(a("").addClass("prev").attr("data-action","previous").append(a("").addClass(d.icons.previous))).append(a("").addClass("picker-switch").attr("data-action","pickerSwitch").attr("colspan",d.calendarWeeks?"6":"5")).append(a("").addClass("next").attr("data-action","next").append(a("").addClass(d.icons.next)))),c=a("").append(a("").append(a("").attr("colspan",d.calendarWeeks?"8":"7")));return[a("
      ").addClass("datepicker-days").append(a("").addClass("table-condensed").append(b).append(a(""))),a("
      ").addClass("datepicker-months").append(a("
      ").addClass("table-condensed").append(b.clone()).append(c.clone())),a("
      ").addClass("datepicker-years").append(a("
      ").addClass("table-condensed").append(b.clone()).append(c.clone())),a("
      ").addClass("datepicker-decades").append(a("
      ").addClass("table-condensed").append(b.clone()).append(c.clone()))]},C=function(){var b=a(""),c=a(""),e=a("");return y("h")&&(b.append(a("
      ").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementHour}).addClass("btn").attr("data-action","incrementHours").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-hour").attr({"data-time-component":"hours",title:d.tooltips.pickHour}).attr("data-action","showHours"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementHour}).addClass("btn").attr("data-action","decrementHours").append(a("").addClass(d.icons.down))))),y("m")&&(y("h")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementMinute}).addClass("btn").attr("data-action","incrementMinutes").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-minute").attr({"data-time-component":"minutes",title:d.tooltips.pickMinute}).attr("data-action","showMinutes"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementMinute}).addClass("btn").attr("data-action","decrementMinutes").append(a("").addClass(d.icons.down))))),y("s")&&(y("m")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementSecond}).addClass("btn").attr("data-action","incrementSeconds").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-second").attr({"data-time-component":"seconds",title:d.tooltips.pickSecond}).attr("data-action","showSeconds"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementSecond}).addClass("btn").attr("data-action","decrementSeconds").append(a("").addClass(d.icons.down))))),h||(b.append(a("").addClass("separator")),c.append(a("").append(a("").addClass("separator"))),a("
      ").addClass("timepicker-picker").append(a("").addClass("table-condensed").append([b,c,e]))},D=function(){var b=a("
      ").addClass("timepicker-hours").append(a("
      ").addClass("table-condensed")),c=a("
      ").addClass("timepicker-minutes").append(a("
      ").addClass("table-condensed")),d=a("
      ").addClass("timepicker-seconds").append(a("
      ").addClass("table-condensed")),e=[C()];return y("h")&&e.push(b),y("m")&&e.push(c),y("s")&&e.push(d),e},E=function(){var b=[];return d.showTodayButton&&b.push(a("
      ").append(a("").attr({"data-action":"today",title:d.tooltips.today}).append(a("").addClass(d.icons.today)))),!d.sideBySide&&A()&&z()&&b.push(a("").append(a("").attr({"data-action":"togglePicker",title:d.tooltips.selectTime}).append(a("").addClass(d.icons.time)))),d.showClear&&b.push(a("").append(a("").attr({"data-action":"clear",title:d.tooltips.clear}).append(a("").addClass(d.icons.clear)))),d.showClose&&b.push(a("").append(a("").attr({"data-action":"close",title:d.tooltips.close}).append(a("").addClass(d.icons.close)))),a("").addClass("table-condensed").append(a("").append(a("").append(b)))},F=function(){var b=a("
      ").addClass("bootstrap-datetimepicker-widget dropdown-menu"),c=a("
      ").addClass("datepicker").append(B()),e=a("
      ").addClass("timepicker").append(D()),f=a("
        ").addClass("list-unstyled"),g=a("
      • ").addClass("picker-switch"+(d.collapse?" accordion-toggle":"")).append(E());return d.inline&&b.removeClass("dropdown-menu"),h&&b.addClass("usetwentyfour"),y("s")&&!h&&b.addClass("wider"),d.sideBySide&&A()&&z()?(b.addClass("timepicker-sbs"),"top"===d.toolbarPlacement&&b.append(g),b.append(a("
        ").addClass("row").append(c.addClass("col-md-6")).append(e.addClass("col-md-6"))),"bottom"===d.toolbarPlacement&&b.append(g),b):("top"===d.toolbarPlacement&&f.append(g),A()&&f.append(a("
      • ").addClass(d.collapse&&z()?"collapse in":"").append(c)),"default"===d.toolbarPlacement&&f.append(g),z()&&f.append(a("
      • ").addClass(d.collapse&&A()?"collapse":"").append(e)),"bottom"===d.toolbarPlacement&&f.append(g),b.append(f))},G=function(){var b,e={};return b=c.is("input")||d.inline?c.data():c.find("input").data(),b.dateOptions&&b.dateOptions instanceof Object&&(e=a.extend(!0,e,b.dateOptions)),a.each(d,function(a){var c="date"+a.charAt(0).toUpperCase()+a.slice(1);void 0!==b[c]&&(e[a]=b[c])}),e},H=function(){var b,e=(n||c).position(),f=(n||c).offset(),g=d.widgetPositioning.vertical,h=d.widgetPositioning.horizontal;if(d.widgetParent)b=d.widgetParent.append(o);else if(c.is("input"))b=c.after(o).parent();else{if(d.inline)return void(b=c.append(o));b=c,c.children().first().after(o)}if("auto"===g&&(g=f.top+1.5*o.height()>=a(window).height()+a(window).scrollTop()&&o.height()+c.outerHeight()a(window).width()?"right":"left"),"top"===g?o.addClass("top").removeClass("bottom"):o.addClass("bottom").removeClass("top"),"right"===h?o.addClass("pull-right"):o.removeClass("pull-right"),"relative"!==b.css("position")&&(b=b.parents().filter(function(){return"relative"===a(this).css("position")}).first()),0===b.length)throw new Error("datetimepicker component should be placed within a relative positioned container");o.css({top:"top"===g?"auto":e.top+c.outerHeight(),bottom:"top"===g?e.top+c.outerHeight():"auto",left:"left"===h?b===c?0:e.left:"auto",right:"left"===h?"auto":b.outerWidth()-c.outerWidth()-(b===c?0:e.left)})},I=function(a){"dp.change"===a.type&&(a.date&&a.date.isSame(a.oldDate)||!a.date&&!a.oldDate)||c.trigger(a)},J=function(a){"y"===a&&(a="YYYY"),I({type:"dp.update",change:a,viewDate:f.clone()})},K=function(a){o&&(a&&(k=Math.max(p,Math.min(3,k+a))),o.find(".datepicker > div").hide().filter(".datepicker-"+q[k].clsName).show())},L=function(){var b=a("
      "),c=f.clone().startOf("w").startOf("d");for(d.calendarWeeks===!0&&b.append(a(""),d.calendarWeeks&&c.append('"),k.push(c)),g="",b.isBefore(f,"M")&&(g+=" old"),b.isAfter(f,"M")&&(g+=" new"),b.isSame(e,"d")&&!m&&(g+=" active"),Q(b,"d")||(g+=" disabled"),b.isSame(x(),"d")&&(g+=" today"),(0===b.day()||6===b.day())&&(g+=" weekend"),c.append('"),b.add(1,"d");i.find("tbody").empty().append(k),S(),T(),U()}},W=function(){var b=o.find(".timepicker-hours table"),c=f.clone().startOf("d"),d=[],e=a("");for(f.hour()>11&&!h&&c.hour(12);c.isSame(f,"d")&&(h||f.hour()<12&&c.hour()<12||f.hour()>11);)c.hour()%4===0&&(e=a(""),d.push(e)),e.append('"),c.add(1,"h");b.empty().append(d)},X=function(){for(var b=o.find(".timepicker-minutes table"),c=f.clone().startOf("h"),e=[],g=a(""),h=1===d.stepping?5:d.stepping;f.isSame(c,"h");)c.minute()%(4*h)===0&&(g=a(""),e.push(g)),g.append('"),c.add(h,"m");b.empty().append(e)},Y=function(){for(var b=o.find(".timepicker-seconds table"),c=f.clone().startOf("m"),d=[],e=a("");f.isSame(c,"m");)c.second()%20===0&&(e=a(""),d.push(e)),e.append('"),c.add(5,"s");b.empty().append(d)},Z=function(){var a,b,c=o.find(".timepicker span[data-time-component]");h||(a=o.find(".timepicker [data-action=togglePeriod]"),b=e.clone().add(e.hours()>=12?-12:12,"h"),a.text(e.format("A")),Q(b,"h")?a.removeClass("disabled"):a.addClass("disabled")),c.filter("[data-time-component=hours]").text(e.format(h?"HH":"hh")),c.filter("[data-time-component=minutes]").text(e.format("mm")),c.filter("[data-time-component=seconds]").text(e.format("ss")),W(),X(),Y()},$=function(){o&&(V(),Z())},_=function(a){var b=m?null:e;return a?(a=a.clone().locale(d.locale),1!==d.stepping&&a.minutes(Math.round(a.minutes()/d.stepping)*d.stepping%60).seconds(0),void(Q(a)?(e=a,f=e.clone(),g.val(e.format(i)),c.data("date",e.format(i)),m=!1,$(),I({type:"dp.change",date:e.clone(),oldDate:b})):(d.keepInvalid||g.val(m?"":e.format(i)),I({type:"dp.error",date:a})))):(m=!0,g.val(""),c.data("date",""),I({type:"dp.change",date:!1,oldDate:b}),void $())},aa=function(){var b=!1;return o?(o.find(".collapse").each(function(){var c=a(this).data("collapse");return c&&c.transitioning?(b=!0,!1):!0}),b?l:(n&&n.hasClass("btn")&&n.toggleClass("active"),o.hide(),a(window).off("resize",H),o.off("click","[data-action]"),o.off("mousedown",!1),o.remove(),o=!1,I({type:"dp.hide",date:e.clone()}),g.blur(),l)):l},ba=function(){_(null)},ca={next:function(){var a=q[k].navFnc;f.add(q[k].navStep,a),V(),J(a)},previous:function(){var a=q[k].navFnc;f.subtract(q[k].navStep,a),V(),J(a)},pickerSwitch:function(){K(1)},selectMonth:function(b){var c=a(b.target).closest("tbody").find("span").index(a(b.target));f.month(c),k===p?(_(e.clone().year(f.year()).month(f.month())),d.inline||aa()):(K(-1),V()),J("M")},selectYear:function(b){var c=parseInt(a(b.target).text(),10)||0;f.year(c),k===p?(_(e.clone().year(f.year())),d.inline||aa()):(K(-1),V()),J("YYYY")},selectDecade:function(b){var c=parseInt(a(b.target).data("selection"),10)||0;f.year(c),k===p?(_(e.clone().year(f.year())),d.inline||aa()):(K(-1),V()),J("YYYY")},selectDay:function(b){var c=f.clone();a(b.target).is(".old")&&c.subtract(1,"M"),a(b.target).is(".new")&&c.add(1,"M"),_(c.date(parseInt(a(b.target).text(),10))),z()||d.keepOpen||d.inline||aa()},incrementHours:function(){var a=e.clone().add(1,"h");Q(a,"h")&&_(a)},incrementMinutes:function(){var a=e.clone().add(d.stepping,"m");Q(a,"m")&&_(a)},incrementSeconds:function(){var a=e.clone().add(1,"s");Q(a,"s")&&_(a)},decrementHours:function(){var a=e.clone().subtract(1,"h");Q(a,"h")&&_(a)},decrementMinutes:function(){var a=e.clone().subtract(d.stepping,"m");Q(a,"m")&&_(a)},decrementSeconds:function(){var a=e.clone().subtract(1,"s");Q(a,"s")&&_(a)},togglePeriod:function(){_(e.clone().add(e.hours()>=12?-12:12,"h"))},togglePicker:function(b){var c,e=a(b.target),f=e.closest("ul"),g=f.find(".in"),h=f.find(".collapse:not(.in)");if(g&&g.length){if(c=g.data("collapse"),c&&c.transitioning)return;g.collapse?(g.collapse("hide"),h.collapse("show")):(g.removeClass("in"),h.addClass("in")),e.is("span")?e.toggleClass(d.icons.time+" "+d.icons.date):e.find("span").toggleClass(d.icons.time+" "+d.icons.date)}},showPicker:function(){o.find(".timepicker > div:not(.timepicker-picker)").hide(),o.find(".timepicker .timepicker-picker").show()},showHours:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-hours").show()},showMinutes:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-minutes").show()},showSeconds:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-seconds").show()},selectHour:function(b){var c=parseInt(a(b.target).text(),10);h||(e.hours()>=12?12!==c&&(c+=12):12===c&&(c=0)),_(e.clone().hours(c)),ca.showPicker.call(l)},selectMinute:function(b){_(e.clone().minutes(parseInt(a(b.target).text(),10))),ca.showPicker.call(l)},selectSecond:function(b){_(e.clone().seconds(parseInt(a(b.target).text(),10))),ca.showPicker.call(l)},clear:ba,today:function(){var a=x();Q(a,"d")&&_(a)},close:aa},da=function(b){return a(b.currentTarget).is(".disabled")?!1:(ca[a(b.currentTarget).data("action")].apply(l,arguments),!1)},ea=function(){var b,c={year:function(a){return a.month(0).date(1).hours(0).seconds(0).minutes(0)},month:function(a){return a.date(1).hours(0).seconds(0).minutes(0)},day:function(a){return a.hours(0).seconds(0).minutes(0)},hour:function(a){return a.seconds(0).minutes(0)},minute:function(a){return a.seconds(0)}};return g.prop("disabled")||!d.ignoreReadonly&&g.prop("readonly")||o?l:(void 0!==g.val()&&0!==g.val().trim().length?_(ga(g.val().trim())):d.useCurrent&&m&&(g.is("input")&&0===g.val().trim().length||d.inline)&&(b=x(),"string"==typeof d.useCurrent&&(b=c[d.useCurrent](b)),_(b)),o=F(),L(),R(),o.find(".timepicker-hours").hide(),o.find(".timepicker-minutes").hide(),o.find(".timepicker-seconds").hide(),$(),K(),a(window).on("resize",H),o.on("click","[data-action]",da),o.on("mousedown",!1),n&&n.hasClass("btn")&&n.toggleClass("active"),o.show(),H(),d.focusOnShow&&!g.is(":focus")&&g.focus(),I({type:"dp.show"}),l)},fa=function(){return o?aa():ea()},ga=function(a){return a=void 0===d.parseInputDate?b.isMoment(a)||a instanceof Date?b(a):x(a):d.parseInputDate(a),a.locale(d.locale),a},ha=function(a){var b,c,e,f,g=null,h=[],i={},j=a.which,k="p";w[j]=k;for(b in w)w.hasOwnProperty(b)&&w[b]===k&&(h.push(b),parseInt(b,10)!==j&&(i[b]=!0));for(b in d.keyBinds)if(d.keyBinds.hasOwnProperty(b)&&"function"==typeof d.keyBinds[b]&&(e=b.split(" "),e.length===h.length&&v[j]===e[e.length-1])){for(f=!0,c=e.length-2;c>=0;c--)if(!(v[e[c]]in i)){f=!1;break}if(f){g=d.keyBinds[b];break}}g&&(g.call(l,o),a.stopPropagation(),a.preventDefault())},ia=function(a){w[a.which]="r",a.stopPropagation(),a.preventDefault()},ja=function(b){var c=a(b.target).val().trim(),d=c?ga(c):null;return _(d),b.stopImmediatePropagation(),!1},ka=function(){g.on({change:ja,blur:d.debug?"":aa,keydown:ha,keyup:ia,focus:d.allowInputToggle?ea:""}),c.is("input")?g.on({focus:ea}):n&&(n.on("click",fa),n.on("mousedown",!1))},la=function(){g.off({change:ja,blur:blur,keydown:ha,keyup:ia,focus:d.allowInputToggle?aa:""}),c.is("input")?g.off({focus:ea}):n&&(n.off("click",fa),n.off("mousedown",!1))},ma=function(b){var c={};return a.each(b,function(){var a=ga(this);a.isValid()&&(c[a.format("YYYY-MM-DD")]=!0)}),Object.keys(c).length?c:!1},na=function(b){var c={};return a.each(b,function(){c[this]=!0}),Object.keys(c).length?c:!1},oa=function(){var a=d.format||"L LT";i=a.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){var b=e.localeData().longDateFormat(a)||a;return b.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){return e.localeData().longDateFormat(a)||a})}),j=d.extraFormats?d.extraFormats.slice():[],j.indexOf(a)<0&&j.indexOf(i)<0&&j.push(i),h=i.toLowerCase().indexOf("a")<1&&i.replace(/\[.*?\]/g,"").indexOf("h")<1,y("y")&&(p=2),y("M")&&(p=1),y("d")&&(p=0),k=Math.max(p,k),m||_(e)};if(l.destroy=function(){aa(),la(),c.removeData("DateTimePicker"),c.removeData("date")},l.toggle=fa,l.show=ea,l.hide=aa,l.disable=function(){return aa(),n&&n.hasClass("btn")&&n.addClass("disabled"),g.prop("disabled",!0),l},l.enable=function(){return n&&n.hasClass("btn")&&n.removeClass("disabled"),g.prop("disabled",!1),l},l.ignoreReadonly=function(a){if(0===arguments.length)return d.ignoreReadonly;if("boolean"!=typeof a)throw new TypeError("ignoreReadonly () expects a boolean parameter");return d.ignoreReadonly=a,l},l.options=function(b){if(0===arguments.length)return a.extend(!0,{},d);if(!(b instanceof Object))throw new TypeError("options() options parameter should be an object");return a.extend(!0,d,b),a.each(d,function(a,b){if(void 0===l[a])throw new TypeError("option "+a+" is not recognized!");l[a](b)}),l},l.date=function(a){if(0===arguments.length)return m?null:e.clone();if(!(null===a||"string"==typeof a||b.isMoment(a)||a instanceof Date))throw new TypeError("date() parameter must be one of [null, string, moment or Date]");return _(null===a?null:ga(a)),l},l.format=function(a){if(0===arguments.length)return d.format;if("string"!=typeof a&&("boolean"!=typeof a||a!==!1))throw new TypeError("format() expects a sting or boolean:false parameter "+a);return d.format=a,i&&oa(),l},l.timeZone=function(a){return 0===arguments.length?d.timeZone:(d.timeZone=a,l)},l.dayViewHeaderFormat=function(a){if(0===arguments.length)return d.dayViewHeaderFormat;if("string"!=typeof a)throw new TypeError("dayViewHeaderFormat() expects a string parameter");return d.dayViewHeaderFormat=a,l},l.extraFormats=function(a){if(0===arguments.length)return d.extraFormats;if(a!==!1&&!(a instanceof Array))throw new TypeError("extraFormats() expects an array or false parameter");return d.extraFormats=a,j&&oa(),l},l.disabledDates=function(b){if(0===arguments.length)return d.disabledDates?a.extend({},d.disabledDates):d.disabledDates;if(!b)return d.disabledDates=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledDates() expects an array parameter");return d.disabledDates=ma(b),d.enabledDates=!1,$(),l},l.enabledDates=function(b){if(0===arguments.length)return d.enabledDates?a.extend({},d.enabledDates):d.enabledDates;if(!b)return d.enabledDates=!1,$(),l;if(!(b instanceof Array))throw new TypeError("enabledDates() expects an array parameter");return d.enabledDates=ma(b),d.disabledDates=!1,$(),l},l.daysOfWeekDisabled=function(a){if(0===arguments.length)return d.daysOfWeekDisabled.splice(0);if("boolean"==typeof a&&!a)return d.daysOfWeekDisabled=!1,$(),l;if(!(a instanceof Array))throw new TypeError("daysOfWeekDisabled() expects an array parameter");if(d.daysOfWeekDisabled=a.reduce(function(a,b){return b=parseInt(b,10),b>6||0>b||isNaN(b)?a:(-1===a.indexOf(b)&&a.push(b),a)},[]).sort(),d.useCurrent&&!d.keepInvalid){for(var b=0;!Q(e,"d");){if(e.add(1,"d"),7===b)throw"Tried 7 times to find a valid date";b++}_(e)}return $(),l},l.maxDate=function(a){if(0===arguments.length)return d.maxDate?d.maxDate.clone():d.maxDate;if("boolean"==typeof a&&a===!1)return d.maxDate=!1,$(),l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("maxDate() Could not parse date parameter: "+a);if(d.minDate&&b.isBefore(d.minDate))throw new TypeError("maxDate() date parameter is before options.minDate: "+b.format(i));return d.maxDate=b,d.useCurrent&&!d.keepInvalid&&e.isAfter(a)&&_(d.maxDate),f.isAfter(b)&&(f=b.clone().subtract(d.stepping,"m")),$(),l},l.minDate=function(a){if(0===arguments.length)return d.minDate?d.minDate.clone():d.minDate;if("boolean"==typeof a&&a===!1)return d.minDate=!1,$(),l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("minDate() Could not parse date parameter: "+a);if(d.maxDate&&b.isAfter(d.maxDate))throw new TypeError("minDate() date parameter is after options.maxDate: "+b.format(i));return d.minDate=b,d.useCurrent&&!d.keepInvalid&&e.isBefore(a)&&_(d.minDate),f.isBefore(b)&&(f=b.clone().add(d.stepping,"m")),$(),l},l.defaultDate=function(a){if(0===arguments.length)return d.defaultDate?d.defaultDate.clone():d.defaultDate;if(!a)return d.defaultDate=!1,l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("defaultDate() Could not parse date parameter: "+a);if(!Q(b))throw new TypeError("defaultDate() date passed is invalid according to component setup validations");return d.defaultDate=b,(d.defaultDate&&d.inline||""===g.val().trim())&&_(d.defaultDate),l},l.locale=function(a){if(0===arguments.length)return d.locale;if(!b.localeData(a))throw new TypeError("locale() locale "+a+" is not loaded from moment locales!");return d.locale=a,e.locale(d.locale),f.locale(d.locale),i&&oa(),o&&(aa(),ea()),l},l.stepping=function(a){return 0===arguments.length?d.stepping:(a=parseInt(a,10),(isNaN(a)||1>a)&&(a=1),d.stepping=a,l)},l.useCurrent=function(a){var b=["year","month","day","hour","minute"];if(0===arguments.length)return d.useCurrent;if("boolean"!=typeof a&&"string"!=typeof a)throw new TypeError("useCurrent() expects a boolean or string parameter");if("string"==typeof a&&-1===b.indexOf(a.toLowerCase()))throw new TypeError("useCurrent() expects a string parameter of "+b.join(", "));return d.useCurrent=a,l},l.collapse=function(a){if(0===arguments.length)return d.collapse;if("boolean"!=typeof a)throw new TypeError("collapse() expects a boolean parameter");return d.collapse===a?l:(d.collapse=a,o&&(aa(),ea()),l)},l.icons=function(b){if(0===arguments.length)return a.extend({},d.icons);if(!(b instanceof Object))throw new TypeError("icons() expects parameter to be an Object");return a.extend(d.icons,b),o&&(aa(),ea()),l},l.tooltips=function(b){if(0===arguments.length)return a.extend({},d.tooltips);if(!(b instanceof Object))throw new TypeError("tooltips() expects parameter to be an Object");return a.extend(d.tooltips,b),o&&(aa(),ea()),l},l.useStrict=function(a){if(0===arguments.length)return d.useStrict;if("boolean"!=typeof a)throw new TypeError("useStrict() expects a boolean parameter");return d.useStrict=a,l},l.sideBySide=function(a){if(0===arguments.length)return d.sideBySide;if("boolean"!=typeof a)throw new TypeError("sideBySide() expects a boolean parameter");return d.sideBySide=a,o&&(aa(),ea()),l},l.viewMode=function(a){if(0===arguments.length)return d.viewMode;if("string"!=typeof a)throw new TypeError("viewMode() expects a string parameter");if(-1===r.indexOf(a))throw new TypeError("viewMode() parameter must be one of ("+r.join(", ")+") value");return d.viewMode=a,k=Math.max(r.indexOf(a),p),K(),l},l.toolbarPlacement=function(a){if(0===arguments.length)return d.toolbarPlacement;if("string"!=typeof a)throw new TypeError("toolbarPlacement() expects a string parameter");if(-1===u.indexOf(a))throw new TypeError("toolbarPlacement() parameter must be one of ("+u.join(", ")+") value");return d.toolbarPlacement=a,o&&(aa(),ea()),l},l.widgetPositioning=function(b){if(0===arguments.length)return a.extend({},d.widgetPositioning);if("[object Object]"!=={}.toString.call(b))throw new TypeError("widgetPositioning() expects an object variable");if(b.horizontal){if("string"!=typeof b.horizontal)throw new TypeError("widgetPositioning() horizontal variable must be a string");if(b.horizontal=b.horizontal.toLowerCase(),-1===t.indexOf(b.horizontal))throw new TypeError("widgetPositioning() expects horizontal parameter to be one of ("+t.join(", ")+")");d.widgetPositioning.horizontal=b.horizontal}if(b.vertical){if("string"!=typeof b.vertical)throw new TypeError("widgetPositioning() vertical variable must be a string");if(b.vertical=b.vertical.toLowerCase(),-1===s.indexOf(b.vertical))throw new TypeError("widgetPositioning() expects vertical parameter to be one of ("+s.join(", ")+")");d.widgetPositioning.vertical=b.vertical}return $(),l},l.calendarWeeks=function(a){if(0===arguments.length)return d.calendarWeeks;if("boolean"!=typeof a)throw new TypeError("calendarWeeks() expects parameter to be a boolean value");return d.calendarWeeks=a,$(),l},l.showTodayButton=function(a){if(0===arguments.length)return d.showTodayButton;if("boolean"!=typeof a)throw new TypeError("showTodayButton() expects a boolean parameter");return d.showTodayButton=a,o&&(aa(),ea()),l},l.showClear=function(a){if(0===arguments.length)return d.showClear;if("boolean"!=typeof a)throw new TypeError("showClear() expects a boolean parameter");return d.showClear=a,o&&(aa(),ea()),l},l.widgetParent=function(b){if(0===arguments.length)return d.widgetParent;if("string"==typeof b&&(b=a(b)),null!==b&&"string"!=typeof b&&!(b instanceof a))throw new TypeError("widgetParent() expects a string or a jQuery object parameter");return d.widgetParent=b,o&&(aa(),ea()),l},l.keepOpen=function(a){if(0===arguments.length)return d.keepOpen;if("boolean"!=typeof a)throw new TypeError("keepOpen() expects a boolean parameter");return d.keepOpen=a,l},l.focusOnShow=function(a){if(0===arguments.length)return d.focusOnShow;if("boolean"!=typeof a)throw new TypeError("focusOnShow() expects a boolean parameter");return d.focusOnShow=a,l},l.inline=function(a){if(0===arguments.length)return d.inline;if("boolean"!=typeof a)throw new TypeError("inline() expects a boolean parameter");return d.inline=a,l},l.clear=function(){return ba(),l},l.keyBinds=function(a){return d.keyBinds=a,l},l.getMoment=function(a){return x(a)},l.debug=function(a){if("boolean"!=typeof a)throw new TypeError("debug() expects a boolean parameter");return d.debug=a,l},l.allowInputToggle=function(a){if(0===arguments.length)return d.allowInputToggle;if("boolean"!=typeof a)throw new TypeError("allowInputToggle() expects a boolean parameter");return d.allowInputToggle=a,l},l.showClose=function(a){if(0===arguments.length)return d.showClose;if("boolean"!=typeof a)throw new TypeError("showClose() expects a boolean parameter");return d.showClose=a,l},l.keepInvalid=function(a){if(0===arguments.length)return d.keepInvalid;if("boolean"!=typeof a)throw new TypeError("keepInvalid() expects a boolean parameter");return d.keepInvalid=a,l},l.datepickerInput=function(a){if(0===arguments.length)return d.datepickerInput;if("string"!=typeof a)throw new TypeError("datepickerInput() expects a string parameter");return d.datepickerInput=a,l},l.parseInputDate=function(a){if(0===arguments.length)return d.parseInputDate; -if("function"!=typeof a)throw new TypeError("parseInputDate() sholud be as function");return d.parseInputDate=a,l},l.disabledTimeIntervals=function(b){if(0===arguments.length)return d.disabledTimeIntervals?a.extend({},d.disabledTimeIntervals):d.disabledTimeIntervals;if(!b)return d.disabledTimeIntervals=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledTimeIntervals() expects an array parameter");return d.disabledTimeIntervals=b,$(),l},l.disabledHours=function(b){if(0===arguments.length)return d.disabledHours?a.extend({},d.disabledHours):d.disabledHours;if(!b)return d.disabledHours=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledHours() expects an array parameter");if(d.disabledHours=na(b),d.enabledHours=!1,d.useCurrent&&!d.keepInvalid){for(var c=0;!Q(e,"h");){if(e.add(1,"h"),24===c)throw"Tried 24 times to find a valid date";c++}_(e)}return $(),l},l.enabledHours=function(b){if(0===arguments.length)return d.enabledHours?a.extend({},d.enabledHours):d.enabledHours;if(!b)return d.enabledHours=!1,$(),l;if(!(b instanceof Array))throw new TypeError("enabledHours() expects an array parameter");if(d.enabledHours=na(b),d.disabledHours=!1,d.useCurrent&&!d.keepInvalid){for(var c=0;!Q(e,"h");){if(e.add(1,"h"),24===c)throw"Tried 24 times to find a valid date";c++}_(e)}return $(),l},l.viewDate=function(a){if(0===arguments.length)return f.clone();if(!a)return f=e.clone(),l;if(!("string"==typeof a||b.isMoment(a)||a instanceof Date))throw new TypeError("viewDate() parameter must be one of [string, moment or Date]");return f=ga(a),J(),l},c.is("input"))g=c;else if(g=c.find(d.datepickerInput),0===g.size())g=c.find("input");else if(!g.is("input"))throw new Error('CSS class "'+d.datepickerInput+'" cannot be applied to non input element');if(c.hasClass("input-group")&&(n=0===c.find(".datepickerbutton").size()?c.find(".input-group-addon"):c.find(".datepickerbutton")),!d.inline&&!g.is("input"))throw new Error("Could not initialize DateTimePicker without an input element");return e=x(),f=e.clone(),a.extend(!0,d,G()),l.options(d),oa(),ka(),g.prop("disabled")&&l.disable(),g.is("input")&&0!==g.val().trim().length?_(ga(g.val().trim())):d.defaultDate&&void 0===g.attr("placeholder")&&_(d.defaultDate),d.inline&&ea(),l};a.fn.datetimepicker=function(b){return this.each(function(){var d=a(this);d.data("DateTimePicker")||(b=a.extend(!0,{},a.fn.datetimepicker.defaults,b),d.data("DateTimePicker",c(d,b)))})},a.fn.datetimepicker.defaults={timeZone:"Etc/UTC",format:!1,dayViewHeaderFormat:"MMMM YYYY",extraFormats:!1,stepping:1,minDate:!1,maxDate:!1,useCurrent:!0,collapse:!0,locale:b.locale(),defaultDate:!1,disabledDates:!1,enabledDates:!1,icons:{time:"glyphicon glyphicon-time",date:"glyphicon glyphicon-calendar",up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down",previous:"glyphicon glyphicon-chevron-left",next:"glyphicon glyphicon-chevron-right",today:"glyphicon glyphicon-screenshot",clear:"glyphicon glyphicon-trash",close:"glyphicon glyphicon-remove"},tooltips:{today:"Go to today",clear:"Clear selection",close:"Close the picker",selectMonth:"Select Month",prevMonth:"Previous Month",nextMonth:"Next Month",selectYear:"Select Year",prevYear:"Previous Year",nextYear:"Next Year",selectDecade:"Select Decade",prevDecade:"Previous Decade",nextDecade:"Next Decade",prevCentury:"Previous Century",nextCentury:"Next Century",pickHour:"Pick Hour",incrementHour:"Increment Hour",decrementHour:"Decrement Hour",pickMinute:"Pick Minute",incrementMinute:"Increment Minute",decrementMinute:"Decrement Minute",pickSecond:"Pick Second",incrementSecond:"Increment Second",decrementSecond:"Decrement Second",togglePeriod:"Toggle Period",selectTime:"Select Time"},useStrict:!1,sideBySide:!1,daysOfWeekDisabled:!1,calendarWeeks:!1,viewMode:"days",toolbarPlacement:"default",showTodayButton:!1,showClear:!1,showClose:!1,widgetPositioning:{horizontal:"auto",vertical:"auto"},widgetParent:null,ignoreReadonly:!1,keepOpen:!1,focusOnShow:!0,inline:!1,keepInvalid:!1,datepickerInput:".datepickerinput",keyBinds:{up:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().subtract(7,"d")):this.date(b.clone().add(this.stepping(),"m"))}},down:function(a){if(!a)return void this.show();var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().add(7,"d")):this.date(b.clone().subtract(this.stepping(),"m"))},"control up":function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().subtract(1,"y")):this.date(b.clone().add(1,"h"))}},"control down":function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().add(1,"y")):this.date(b.clone().subtract(1,"h"))}},left:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().subtract(1,"d"))}},right:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().add(1,"d"))}},pageUp:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().subtract(1,"M"))}},pageDown:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().add(1,"M"))}},enter:function(){this.hide()},escape:function(){this.hide()},"control space":function(a){a.find(".timepicker").is(":visible")&&a.find('.btn[data-action="togglePeriod"]').click()},t:function(){this.date(this.getMoment())},"delete":function(){this.clear()}},debug:!1,allowInputToggle:!1,disabledTimeIntervals:!1,disabledHours:!1,enabledHours:!1,viewDate:!1}}); \ No newline at end of file diff --git a/static/moment-fr.js b/static/moment-fr.js deleted file mode 100644 index 143fca16..00000000 --- a/static/moment-fr.js +++ /dev/null @@ -1,64 +0,0 @@ -//! 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; - -})); diff --git a/static/moment-timezone-with-data-2010-2020.js b/static/moment-timezone-with-data-2010-2020.js deleted file mode 100644 index 7e58bb50..00000000 --- a/static/moment-timezone-with-data-2010-2020.js +++ /dev/null @@ -1,1196 +0,0 @@ -//! moment-timezone.js -//! version : 0.5.5 -//! author : Tim Wood -//! license : MIT -//! github.com/moment/moment-timezone - -(function (root, factory) { - "use strict"; - - /*global define*/ - if (typeof define === 'function' && define.amd) { - define(['moment'], factory); // AMD - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(require('moment')); // Node - } else { - factory(root.moment); // Browser - } -}(this, function (moment) { - "use strict"; - - // Do not load moment-timezone a second time. - if (moment.tz !== undefined) { - logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion); - return moment; - } - - var VERSION = "0.5.5", - zones = {}, - links = {}, - names = {}, - guesses = {}, - cachedGuess, - - momentVersion = moment.version.split('.'), - major = +momentVersion[0], - minor = +momentVersion[1]; - - // Moment.js version check - if (major < 2 || (major === 2 && minor < 6)) { - logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com'); - } - - /************************************ - Unpacking - ************************************/ - - function charCodeToInt(charCode) { - if (charCode > 96) { - return charCode - 87; - } else if (charCode > 64) { - return charCode - 29; - } - return charCode - 48; - } - - function unpackBase60(string) { - var i = 0, - parts = string.split('.'), - whole = parts[0], - fractional = parts[1] || '', - multiplier = 1, - num, - out = 0, - sign = 1; - - // handle negative numbers - if (string.charCodeAt(0) === 45) { - i = 1; - sign = -1; - } - - // handle digits before the decimal - for (i; i < whole.length; i++) { - num = charCodeToInt(whole.charCodeAt(i)); - out = 60 * out + num; - } - - // handle digits after the decimal - for (i = 0; i < fractional.length; i++) { - multiplier = multiplier / 60; - num = charCodeToInt(fractional.charCodeAt(i)); - out += num * multiplier; - } - - return out * sign; - } - - function arrayToInt (array) { - for (var i = 0; i < array.length; i++) { - array[i] = unpackBase60(array[i]); - } - } - - function intToUntil (array, length) { - for (var i = 0; i < length; i++) { - array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds - } - - array[length - 1] = Infinity; - } - - function mapIndices (source, indices) { - var out = [], i; - - for (i = 0; i < indices.length; i++) { - out[i] = source[indices[i]]; - } - - return out; - } - - function unpack (string) { - var data = string.split('|'), - offsets = data[2].split(' '), - indices = data[3].split(''), - untils = data[4].split(' '); - - arrayToInt(offsets); - arrayToInt(indices); - arrayToInt(untils); - - intToUntil(untils, indices.length); - - return { - name : data[0], - abbrs : mapIndices(data[1].split(' '), indices), - offsets : mapIndices(offsets, indices), - untils : untils, - population : data[5] | 0 - }; - } - - /************************************ - Zone object - ************************************/ - - function Zone (packedString) { - if (packedString) { - this._set(unpack(packedString)); - } - } - - Zone.prototype = { - _set : function (unpacked) { - this.name = unpacked.name; - this.abbrs = unpacked.abbrs; - this.untils = unpacked.untils; - this.offsets = unpacked.offsets; - this.population = unpacked.population; - }, - - _index : function (timestamp) { - var target = +timestamp, - untils = this.untils, - i; - - for (i = 0; i < untils.length; i++) { - if (target < untils[i]) { - return i; - } - } - }, - - parse : function (timestamp) { - var target = +timestamp, - offsets = this.offsets, - untils = this.untils, - max = untils.length - 1, - offset, offsetNext, offsetPrev, i; - - for (i = 0; i < max; i++) { - offset = offsets[i]; - offsetNext = offsets[i + 1]; - offsetPrev = offsets[i ? i - 1 : i]; - - if (offset < offsetNext && tz.moveAmbiguousForward) { - offset = offsetNext; - } else if (offset > offsetPrev && tz.moveInvalidForward) { - offset = offsetPrev; - } - - if (target < untils[i] - (offset * 60000)) { - return offsets[i]; - } - } - - return offsets[max]; - }, - - abbr : function (mom) { - return this.abbrs[this._index(mom)]; - }, - - offset : function (mom) { - return this.offsets[this._index(mom)]; - } - }; - - /************************************ - Current Timezone - ************************************/ - - function OffsetAt(at) { - var timeString = at.toTimeString(); - var abbr = timeString.match(/\([a-z ]+\)/i); - if (abbr && abbr[0]) { - // 17:56:31 GMT-0600 (CST) - // 17:56:31 GMT-0600 (Central Standard Time) - abbr = abbr[0].match(/[A-Z]/g); - abbr = abbr ? abbr.join('') : undefined; - } else { - // 17:56:31 CST - // 17:56:31 GMT+0800 (台北標準時間) - abbr = timeString.match(/[A-Z]{3,5}/g); - abbr = abbr ? abbr[0] : undefined; - } - - if (abbr === 'GMT') { - abbr = undefined; - } - - this.at = +at; - this.abbr = abbr; - this.offset = at.getTimezoneOffset(); - } - - function ZoneScore(zone) { - this.zone = zone; - this.offsetScore = 0; - this.abbrScore = 0; - } - - ZoneScore.prototype.scoreOffsetAt = function (offsetAt) { - this.offsetScore += Math.abs(this.zone.offset(offsetAt.at) - offsetAt.offset); - if (this.zone.abbr(offsetAt.at).replace(/[^A-Z]/g, '') !== offsetAt.abbr) { - this.abbrScore++; - } - }; - - function findChange(low, high) { - var mid, diff; - - while ((diff = ((high.at - low.at) / 12e4 | 0) * 6e4)) { - mid = new OffsetAt(new Date(low.at + diff)); - if (mid.offset === low.offset) { - low = mid; - } else { - high = mid; - } - } - - return low; - } - - function userOffsets() { - var startYear = new Date().getFullYear() - 2, - last = new OffsetAt(new Date(startYear, 0, 1)), - offsets = [last], - change, next, i; - - for (i = 1; i < 48; i++) { - next = new OffsetAt(new Date(startYear, i, 1)); - if (next.offset !== last.offset) { - change = findChange(last, next); - offsets.push(change); - offsets.push(new OffsetAt(new Date(change.at + 6e4))); - } - last = next; - } - - for (i = 0; i < 4; i++) { - offsets.push(new OffsetAt(new Date(startYear + i, 0, 1))); - offsets.push(new OffsetAt(new Date(startYear + i, 6, 1))); - } - - return offsets; - } - - function sortZoneScores (a, b) { - if (a.offsetScore !== b.offsetScore) { - return a.offsetScore - b.offsetScore; - } - if (a.abbrScore !== b.abbrScore) { - return a.abbrScore - b.abbrScore; - } - return b.zone.population - a.zone.population; - } - - function addToGuesses (name, offsets) { - var i, offset; - arrayToInt(offsets); - for (i = 0; i < offsets.length; i++) { - offset = offsets[i]; - guesses[offset] = guesses[offset] || {}; - guesses[offset][name] = true; - } - } - - function guessesForUserOffsets (offsets) { - var offsetsLength = offsets.length, - filteredGuesses = {}, - out = [], - i, j, guessesOffset; - - for (i = 0; i < offsetsLength; i++) { - guessesOffset = guesses[offsets[i].offset] || {}; - for (j in guessesOffset) { - if (guessesOffset.hasOwnProperty(j)) { - filteredGuesses[j] = true; - } - } - } - - for (i in filteredGuesses) { - if (filteredGuesses.hasOwnProperty(i)) { - out.push(names[i]); - } - } - - return out; - } - - function rebuildGuess () { - - // use Intl API when available and returning valid time zone - try { - var intlName = Intl.DateTimeFormat().resolvedOptions().timeZone; - if (intlName){ - var name = names[normalizeName(intlName)]; - if (name) { - return name; - } - logError("Moment Timezone found " + intlName + " from the Intl api, but did not have that data loaded."); - } - } catch (e) { - // Intl unavailable, fall back to manual guessing. - } - - var offsets = userOffsets(), - offsetsLength = offsets.length, - guesses = guessesForUserOffsets(offsets), - zoneScores = [], - zoneScore, i, j; - - for (i = 0; i < guesses.length; i++) { - zoneScore = new ZoneScore(getZone(guesses[i]), offsetsLength); - for (j = 0; j < offsetsLength; j++) { - zoneScore.scoreOffsetAt(offsets[j]); - } - zoneScores.push(zoneScore); - } - - zoneScores.sort(sortZoneScores); - - return zoneScores.length > 0 ? zoneScores[0].zone.name : undefined; - } - - function guess (ignoreCache) { - if (!cachedGuess || ignoreCache) { - cachedGuess = rebuildGuess(); - } - return cachedGuess; - } - - /************************************ - Global Methods - ************************************/ - - function normalizeName (name) { - return (name || '').toLowerCase().replace(/\//g, '_'); - } - - function addZone (packed) { - var i, name, split, normalized; - - if (typeof packed === "string") { - packed = [packed]; - } - - for (i = 0; i < packed.length; i++) { - split = packed[i].split('|'); - name = split[0]; - normalized = normalizeName(name); - zones[normalized] = packed[i]; - names[normalized] = name; - if (split[5]) { - addToGuesses(normalized, split[2].split(' ')); - } - } - } - - function getZone (name, caller) { - name = normalizeName(name); - - var zone = zones[name]; - var link; - - if (zone instanceof Zone) { - return zone; - } - - if (typeof zone === 'string') { - zone = new Zone(zone); - zones[name] = zone; - return zone; - } - - // Pass getZone to prevent recursion more than 1 level deep - if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) { - zone = zones[name] = new Zone(); - zone._set(link); - zone.name = names[name]; - return zone; - } - - return null; - } - - function getNames () { - var i, out = []; - - for (i in names) { - if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) { - out.push(names[i]); - } - } - - return out.sort(); - } - - function addLink (aliases) { - var i, alias, normal0, normal1; - - if (typeof aliases === "string") { - aliases = [aliases]; - } - - for (i = 0; i < aliases.length; i++) { - alias = aliases[i].split('|'); - - normal0 = normalizeName(alias[0]); - normal1 = normalizeName(alias[1]); - - links[normal0] = normal1; - names[normal0] = alias[0]; - - links[normal1] = normal0; - names[normal1] = alias[1]; - } - } - - function loadData (data) { - addZone(data.zones); - addLink(data.links); - tz.dataVersion = data.version; - } - - function zoneExists (name) { - if (!zoneExists.didShowError) { - zoneExists.didShowError = true; - logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')"); - } - return !!getZone(name); - } - - function needsOffset (m) { - return !!(m._a && (m._tzm === undefined)); - } - - function logError (message) { - if (typeof console !== 'undefined' && typeof console.error === 'function') { - console.error(message); - } - } - - /************************************ - moment.tz namespace - ************************************/ - - function tz (input) { - var args = Array.prototype.slice.call(arguments, 0, -1), - name = arguments[arguments.length - 1], - zone = getZone(name), - out = moment.utc.apply(null, args); - - if (zone && !moment.isMoment(input) && needsOffset(out)) { - out.add(zone.parse(out), 'minutes'); - } - - out.tz(name); - - return out; - } - - tz.version = VERSION; - tz.dataVersion = ''; - tz._zones = zones; - tz._links = links; - tz._names = names; - tz.add = addZone; - tz.link = addLink; - tz.load = loadData; - tz.zone = getZone; - tz.zoneExists = zoneExists; // deprecated in 0.1.0 - tz.guess = guess; - tz.names = getNames; - tz.Zone = Zone; - tz.unpack = unpack; - tz.unpackBase60 = unpackBase60; - tz.needsOffset = needsOffset; - tz.moveInvalidForward = true; - tz.moveAmbiguousForward = false; - - /************************************ - Interface with Moment.js - ************************************/ - - var fn = moment.fn; - - moment.tz = tz; - - moment.defaultZone = null; - - moment.updateOffset = function (mom, keepTime) { - var zone = moment.defaultZone, - offset; - - if (mom._z === undefined) { - if (zone && needsOffset(mom) && !mom._isUTC) { - mom._d = moment.utc(mom._a)._d; - mom.utc().add(zone.parse(mom), 'minutes'); - } - mom._z = zone; - } - if (mom._z) { - offset = mom._z.offset(mom); - if (Math.abs(offset) < 16) { - offset = offset / 60; - } - if (mom.utcOffset !== undefined) { - mom.utcOffset(-offset, keepTime); - } else { - mom.zone(offset, keepTime); - } - } - }; - - fn.tz = function (name) { - if (name) { - this._z = getZone(name); - if (this._z) { - moment.updateOffset(this); - } else { - logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/."); - } - return this; - } - if (this._z) { return this._z.name; } - }; - - function abbrWrap (old) { - return function () { - if (this._z) { return this._z.abbr(this); } - return old.call(this); - }; - } - - function resetZoneWrap (old) { - return function () { - this._z = null; - return old.apply(this, arguments); - }; - } - - fn.zoneName = abbrWrap(fn.zoneName); - fn.zoneAbbr = abbrWrap(fn.zoneAbbr); - fn.utc = resetZoneWrap(fn.utc); - - moment.tz.setDefault = function(name) { - if (major < 2 || (major === 2 && minor < 9)) { - logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.'); - } - moment.defaultZone = name ? getZone(name) : null; - return moment; - }; - - // Cloning a moment should include the _z property. - var momentProperties = moment.momentProperties; - if (Object.prototype.toString.call(momentProperties) === '[object Array]') { - // moment 2.8.1+ - momentProperties.push('_z'); - momentProperties.push('_a'); - } else if (momentProperties) { - // moment 2.7.0 - momentProperties._z = null; - } - - loadData({ - "version": "2016f", - "zones": [ - "Africa/Abidjan|GMT|0|0||48e5", - "Africa/Khartoum|EAT|-30|0||51e5", - "Africa/Algiers|CET|-10|0||26e5", - "Africa/Lagos|WAT|-10|0||17e6", - "Africa/Maputo|CAT|-20|0||26e5", - "Africa/Cairo|EET EEST|-20 -30|010101010|1Cby0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0|15e6", - "Africa/Casablanca|WET WEST|0 -10|01010101010101010101010101010101010101010|1Cco0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0|32e5", - "Europe/Paris|CET CEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e6", - "Africa/Johannesburg|SAST|-20|0||84e5", - "Africa/Tripoli|EET CET CEST|-20 -10 -20|0120|1IlA0 TA0 1o00|11e5", - "Africa/Windhoek|WAST WAT|-20 -10|01010101010101010101010|1C1c0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0|32e4", - "America/Adak|HST HDT|a0 90|01010101010101010101010|1BR00 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|326", - "America/Anchorage|AKST AKDT|90 80|01010101010101010101010|1BQX0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|30e4", - "America/Santo_Domingo|AST|40|0||29e5", - "America/Araguaina|BRT BRST|30 20|010|1IdD0 Lz0|14e4", - "America/Argentina/Buenos_Aires|ART|30|0|", - "America/Asuncion|PYST PYT|30 40|01010101010101010101010|1C430 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0|28e5", - "America/Panama|EST|50|0||15e5", - "America/Bahia|BRT BRST|30 20|010|1FJf0 Rb0|27e5", - "America/Bahia_Banderas|MST CDT CST|70 50 60|01212121212121212121212|1C1l0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|84e3", - "America/Fortaleza|BRT|30|0||34e5", - "America/Managua|CST|60|0||22e5", - "America/Manaus|AMT|40|0||19e5", - "America/Bogota|COT|50|0||90e5", - "America/Denver|MST MDT|70 60|01010101010101010101010|1BQV0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e5", - "America/Campo_Grande|AMST AMT|30 40|01010101010101010101010|1BIr0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10|77e4", - "America/Cancun|CST CDT EST|60 50 50|010101010102|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4", - "America/Caracas|VET VET|4u 40|01|1QMT0|29e5", - "America/Cayenne|GFT|30|0||58e3", - "America/Chicago|CST CDT|60 50|01010101010101010101010|1BQU0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|92e5", - "America/Chihuahua|MST MDT|70 60|01010101010101010101010|1C1l0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|81e4", - "America/Phoenix|MST|70|0||42e5", - "America/Los_Angeles|PST PDT|80 70|01010101010101010101010|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e6", - "America/New_York|EST EDT|50 40|01010101010101010101010|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6", - "America/Rio_Branco|AMT ACT|40 50|01|1KLE0|31e4", - "America/Fort_Nelson|PST PDT MST|80 70 70|010101010102|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2", - "America/Halifax|AST ADT|40 30|01010101010101010101010|1BQS0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|39e4", - "America/Godthab|WGT WGST|30 20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e3", - "America/Goose_Bay|AST ADT|40 30|01010101010101010101010|1BQQ1 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|76e2", - "America/Grand_Turk|EST EDT AST|50 40 40|0101010101012|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e2", - "America/Guayaquil|ECT|50|0||27e5", - "America/Guyana|GYT|40|0||80e4", - "America/Havana|CST CDT|50 40|01010101010101010101010|1BQR0 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0|21e5", - "America/La_Paz|BOT|40|0||19e5", - "America/Lima|PET|50|0||11e6", - "America/Mexico_City|CST CDT|60 50|01010101010101010101010|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|20e6", - "America/Metlakatla|PST AKST AKDT|80 90 80|012121212121|1PAa0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2", - "America/Miquelon|PMST PMDT|30 20|01010101010101010101010|1BQR0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|61e2", - "America/Montevideo|UYST UYT|20 30|010101010101|1BQQ0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0|17e5", - "America/Noronha|FNT|20|0||30e2", - "America/North_Dakota/Beulah|MST MDT CST CDT|70 60 60 50|01232323232323232323232|1BQV0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", - "America/Paramaribo|SRT|30|0||24e4", - "America/Port-au-Prince|EST EDT|50 40|010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5", - "America/Santiago|CLST CLT|30 40|010101010101010101010|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|62e5", - "America/Sao_Paulo|BRST BRT|20 30|01010101010101010101010|1BIq0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10|20e6", - "America/Scoresbysund|EGT EGST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|452", - "America/St_Johns|NST NDT|3u 2u|01010101010101010101010|1BQPv 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4", - "Antarctica/Casey|CAST AWST|-b0 -80|0101|1BN30 40P0 KL0|10", - "Antarctica/Davis|DAVT DAVT|-50 -70|0101|1BPw0 3Wn0 KN0|70", - "Antarctica/DumontDUrville|DDUT|-a0|0||80", - "Antarctica/Macquarie|AEDT MIST|-b0 -b0|01|1C140|1", - "Antarctica/Mawson|MAWT|-50|0||60", - "Pacific/Auckland|NZDT NZST|-d0 -c0|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|14e5", - "Antarctica/Rothera|ROTT|30|0||130", - "Antarctica/Syowa|SYOT|-30|0||20", - "Antarctica/Troll|UTC CEST|0 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|40", - "Antarctica/Vostok|VOST|-60|0||25", - "Asia/Baghdad|AST|-30|0||66e5", - "Asia/Almaty|+06|-60|0||15e5", - "Asia/Amman|EET EEST|-20 -30|010101010101010101010|1BVy0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0|25e5", - "Asia/Anadyr|ANAT ANAST ANAT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0|13e3", - "Asia/Aqtobe|+05|-50|0||27e4", - "Asia/Ashgabat|TMT|-50|0||41e4", - "Asia/Baku|AZT AZST|-40 -50|0101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5", - "Asia/Bangkok|ICT|-70|0||15e6", - "Asia/Barnaul|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 3rd0", - "Asia/Beirut|EET EEST|-20 -30|01010101010101010101010|1BWm0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0|22e5", - "Asia/Bishkek|KGT|-60|0||87e4", - "Asia/Brunei|BNT|-80|0||42e4", - "Asia/Kolkata|IST|-5u|0||15e6", - "Asia/Chita|YAKT YAKST YAKT IRKT|-90 -a0 -a0 -80|010230|1BWh0 1qM0 WM0 8Hz0 3re0|33e4", - "Asia/Choibalsan|CHOT CHOST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|38e3", - "Asia/Shanghai|CST|-80|0||23e6", - "Asia/Dhaka|BDT|-60|0||16e6", - "Asia/Damascus|EET EEST|-20 -30|01010101010101010101010|1C0m0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0|26e5", - "Asia/Dili|TLT|-90|0||19e4", - "Asia/Dubai|GST|-40|0||39e5", - "Asia/Dushanbe|TJT|-50|0||76e4", - "Asia/Gaza|EET EEST|-20 -30|01010101010101010101010|1BVW1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|18e5", - "Asia/Hebron|EET EEST|-20 -30|0101010101010101010101010|1BVy0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|25e4", - "Asia/Hong_Kong|HKT|-80|0||73e5", - "Asia/Hovd|HOVT HOVST|-70 -80|0101010101010|1O8H0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|81e3", - "Asia/Irkutsk|IRKT IRKST IRKT|-80 -90 -90|01020|1BWi0 1qM0 WM0 8Hz0|60e4", - "Europe/Istanbul|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|13e6", - "Asia/Jakarta|WIB|-70|0||31e6", - "Asia/Jayapura|WIT|-90|0||26e4", - "Asia/Jerusalem|IST IDT|-20 -30|01010101010101010101010|1BVA0 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0|81e4", - "Asia/Kabul|AFT|-4u|0||46e5", - "Asia/Kamchatka|PETT PETST PETT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0|18e4", - "Asia/Karachi|PKT|-50|0||24e6", - "Asia/Urumqi|XJT|-60|0||32e5", - "Asia/Kathmandu|NPT|-5J|0||12e5", - "Asia/Khandyga|VLAT VLAST VLAT YAKT YAKT|-a0 -b0 -b0 -a0 -90|010234|1BWg0 1qM0 WM0 17V0 7zD0|66e2", - "Asia/Krasnoyarsk|KRAT KRAST KRAT|-70 -80 -80|01020|1BWj0 1qM0 WM0 8Hz0|10e5", - "Asia/Kuala_Lumpur|MYT|-80|0||71e5", - "Asia/Magadan|MAGT MAGST MAGT MAGT|-b0 -c0 -c0 -a0|010230|1BWf0 1qM0 WM0 8Hz0 3Cq0|95e3", - "Asia/Makassar|WITA|-80|0||15e5", - "Asia/Manila|PHT|-80|0||24e6", - "Europe/Athens|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|35e5", - "Asia/Novokuznetsk|+07 +06|-70 -60|010|1Dp80 WM0|55e4", - "Asia/Novosibirsk|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 4eN0|15e5", - "Asia/Omsk|OMST OMSST OMST|-60 -70 -70|01020|1BWk0 1qM0 WM0 8Hz0|12e5", - "Asia/Pyongyang|KST KST|-90 -8u|01|1P4D0|29e5", - "Asia/Rangoon|MMT|-6u|0||48e5", - "Asia/Sakhalin|SAKT SAKST SAKT|-a0 -b0 -b0|010202|1BWg0 1qM0 WM0 8Hz0 3rd0|58e4", - "Asia/Tashkent|UZT|-50|0||23e5", - "Asia/Seoul|KST|-90|0||23e6", - "Asia/Singapore|SGT|-80|0||56e5", - "Asia/Srednekolymsk|MAGT MAGST MAGT SRET|-b0 -c0 -c0 -b0|01023|1BWf0 1qM0 WM0 8Hz0|35e2", - "Asia/Tbilisi|GET|-40|0||11e5", - "Asia/Tehran|IRST IRDT|-3u -4u|01010101010101010101010|1BTUu 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0|14e6", - "Asia/Thimphu|BTT|-60|0||79e3", - "Asia/Tokyo|JST|-90|0||38e6", - "Asia/Tomsk|+06 +07|-60 -70|010101|1BWk0 1qM0 WM0 8Hz0 3Qp0|10e5", - "Asia/Ulaanbaatar|ULAT ULAST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|12e5", - "Asia/Ust-Nera|MAGT MAGST MAGT VLAT VLAT|-b0 -c0 -c0 -b0 -a0|010234|1BWf0 1qM0 WM0 17V0 7zD0|65e2", - "Asia/Vladivostok|VLAT VLAST VLAT|-a0 -b0 -b0|01020|1BWg0 1qM0 WM0 8Hz0|60e4", - "Asia/Yakutsk|YAKT YAKST YAKT|-90 -a0 -a0|01020|1BWh0 1qM0 WM0 8Hz0|28e4", - "Asia/Yekaterinburg|YEKT YEKST YEKT|-50 -60 -60|01020|1BWl0 1qM0 WM0 8Hz0|14e5", - "Asia/Yerevan|AMT AMST|-40 -50|01010|1BWm0 1qM0 WM0 1qM0|13e5", - "Atlantic/Azores|AZOT AZOST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|25e4", - "Europe/Lisbon|WET WEST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e5", - "Atlantic/Cape_Verde|CVT|10|0||50e4", - "Atlantic/South_Georgia|GST|20|0||30", - "Atlantic/Stanley|FKST FKT|30 40|010|1C6R0 U10|21e2", - "Australia/Sydney|AEDT AEST|-b0 -a0|01010101010101010101010|1C140 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|40e5", - "Australia/Adelaide|ACDT ACST|-au -9u|01010101010101010101010|1C14u 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|11e5", - "Australia/Brisbane|AEST|-a0|0||20e5", - "Australia/Darwin|ACST|-9u|0||12e4", - "Australia/Eucla|ACWST|-8J|0||368", - "Australia/Lord_Howe|LHDT LHST|-b0 -au|01010101010101010101010|1C130 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu|347", - "Australia/Perth|AWST|-80|0||18e5", - "Pacific/Easter|EASST EAST|50 60|010101010101010101010|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|30e2", - "Europe/Dublin|GMT IST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", - "Etc/GMT+1|GMT+1|10|0|", - "Etc/GMT+10|GMT+10|a0|0|", - "Etc/GMT+11|GMT+11|b0|0|", - "Etc/GMT+12|GMT+12|c0|0|", - "Etc/GMT+2|GMT+2|20|0|", - "Etc/GMT+3|GMT+3|30|0|", - "Etc/GMT+4|GMT+4|40|0|", - "Etc/GMT+5|GMT+5|50|0|", - "Etc/GMT+6|GMT+6|60|0|", - "Etc/GMT+7|GMT+7|70|0|", - "Etc/GMT+8|GMT+8|80|0|", - "Etc/GMT+9|GMT+9|90|0|", - "Etc/GMT-1|GMT-1|-10|0|", - "Etc/GMT-10|GMT-10|-a0|0|", - "Etc/GMT-11|GMT-11|-b0|0|", - "Etc/GMT-12|GMT-12|-c0|0|", - "Etc/GMT-13|GMT-13|-d0|0|", - "Etc/GMT-14|GMT-14|-e0|0|", - "Etc/GMT-2|GMT-2|-20|0|", - "Etc/GMT-3|GMT-3|-30|0|", - "Etc/GMT-4|GMT-4|-40|0|", - "Etc/GMT-5|GMT-5|-50|0|", - "Etc/GMT-6|GMT-6|-60|0|", - "Etc/GMT-7|GMT-7|-70|0|", - "Etc/GMT-8|GMT-8|-80|0|", - "Etc/GMT-9|GMT-9|-90|0|", - "Etc/UCT|UCT|0|0|", - "Etc/UTC|UTC|0|0|", - "Europe/Astrakhan|+03 +04|-30 -40|010101|1BWn0 1qM0 WM0 8Hz0 3rd0", - "Europe/London|GMT BST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|10e6", - "Europe/Chisinau|EET EEST|-20 -30|01010101010101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|67e4", - "Europe/Kaliningrad|EET EEST FET|-20 -30 -30|01020|1BWo0 1qM0 WM0 8Hz0|44e4", - "Europe/Kirov|+03 +04|-30 -40|01010|1BWn0 1qM0 WM0 8Hz0|48e4", - "Europe/Minsk|EET EEST FET MSK|-20 -30 -30 -30|01023|1BWo0 1qM0 WM0 8Hy0|19e5", - "Europe/Moscow|MSK MSD MSK|-30 -40 -40|01020|1BWn0 1qM0 WM0 8Hz0|16e6", - "Europe/Samara|SAMT SAMST SAMT|-40 -40 -30|0120|1BWm0 1qN0 WM0|12e5", - "Europe/Simferopol|EET EEST MSK MSK|-20 -30 -40 -30|01010101023|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0|33e4", - "Pacific/Honolulu|HST|a0|0||37e4", - "Indian/Chagos|IOT|-60|0||30e2", - "Indian/Christmas|CXT|-70|0||21e2", - "Indian/Cocos|CCT|-6u|0||596", - "Indian/Kerguelen|TFT|-50|0||130", - "Indian/Mahe|SCT|-40|0||79e3", - "Indian/Maldives|MVT|-50|0||35e4", - "Indian/Mauritius|MUT|-40|0||15e4", - "Indian/Reunion|RET|-40|0||84e4", - "Pacific/Majuro|MHT|-c0|0||28e3", - "MET|MET MEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", - "Pacific/Chatham|CHADT CHAST|-dJ -cJ|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|600", - "Pacific/Apia|SST SDT WSDT WSST|b0 a0 -e0 -d0|01012323232323232323232|1Dbn0 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|37e3", - "Pacific/Bougainville|PGT BST|-a0 -b0|01|1NwE0|18e4", - "Pacific/Chuuk|CHUT|-a0|0||49e3", - "Pacific/Efate|VUT|-b0|0||66e3", - "Pacific/Enderbury|PHOT|-d0|0||1", - "Pacific/Fakaofo|TKT TKT|b0 -d0|01|1Gfn0|483", - "Pacific/Fiji|FJST FJT|-d0 -c0|01010101010101010101010|1BWe0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0|88e4", - "Pacific/Funafuti|TVT|-c0|0||45e2", - "Pacific/Galapagos|GALT|60|0||25e3", - "Pacific/Gambier|GAMT|90|0||125", - "Pacific/Guadalcanal|SBT|-b0|0||11e4", - "Pacific/Guam|ChST|-a0|0||17e4", - "Pacific/Kiritimati|LINT|-e0|0||51e2", - "Pacific/Kosrae|KOST|-b0|0||66e2", - "Pacific/Marquesas|MART|9u|0||86e2", - "Pacific/Pago_Pago|SST|b0|0||37e2", - "Pacific/Nauru|NRT|-c0|0||10e3", - "Pacific/Niue|NUT|b0|0||12e2", - "Pacific/Norfolk|NFT NFT|-bu -b0|01|1PoCu|25e4", - "Pacific/Noumea|NCT|-b0|0||98e3", - "Pacific/Palau|PWT|-90|0||21e3", - "Pacific/Pitcairn|PST|80|0||56", - "Pacific/Pohnpei|PONT|-b0|0||34e3", - "Pacific/Port_Moresby|PGT|-a0|0||25e4", - "Pacific/Rarotonga|CKT|a0|0||13e3", - "Pacific/Tahiti|TAHT|a0|0||18e4", - "Pacific/Tarawa|GILT|-c0|0||29e3", - "Pacific/Tongatapu|TOT|-d0|0||75e3", - "Pacific/Wake|WAKT|-c0|0||16e3", - "Pacific/Wallis|WFT|-c0|0||94" - ], - "links": [ - "Africa/Abidjan|Africa/Accra", - "Africa/Abidjan|Africa/Bamako", - "Africa/Abidjan|Africa/Banjul", - "Africa/Abidjan|Africa/Bissau", - "Africa/Abidjan|Africa/Conakry", - "Africa/Abidjan|Africa/Dakar", - "Africa/Abidjan|Africa/Freetown", - "Africa/Abidjan|Africa/Lome", - "Africa/Abidjan|Africa/Monrovia", - "Africa/Abidjan|Africa/Nouakchott", - "Africa/Abidjan|Africa/Ouagadougou", - "Africa/Abidjan|Africa/Sao_Tome", - "Africa/Abidjan|Africa/Timbuktu", - "Africa/Abidjan|America/Danmarkshavn", - "Africa/Abidjan|Atlantic/Reykjavik", - "Africa/Abidjan|Atlantic/St_Helena", - "Africa/Abidjan|Etc/GMT", - "Africa/Abidjan|Etc/GMT+0", - "Africa/Abidjan|Etc/GMT-0", - "Africa/Abidjan|Etc/GMT0", - "Africa/Abidjan|Etc/Greenwich", - "Africa/Abidjan|GMT", - "Africa/Abidjan|GMT+0", - "Africa/Abidjan|GMT-0", - "Africa/Abidjan|GMT0", - "Africa/Abidjan|Greenwich", - "Africa/Abidjan|Iceland", - "Africa/Algiers|Africa/Tunis", - "Africa/Cairo|Egypt", - "Africa/Casablanca|Africa/El_Aaiun", - "Africa/Johannesburg|Africa/Maseru", - "Africa/Johannesburg|Africa/Mbabane", - "Africa/Khartoum|Africa/Addis_Ababa", - "Africa/Khartoum|Africa/Asmara", - "Africa/Khartoum|Africa/Asmera", - "Africa/Khartoum|Africa/Dar_es_Salaam", - "Africa/Khartoum|Africa/Djibouti", - "Africa/Khartoum|Africa/Juba", - "Africa/Khartoum|Africa/Kampala", - "Africa/Khartoum|Africa/Mogadishu", - "Africa/Khartoum|Africa/Nairobi", - "Africa/Khartoum|Indian/Antananarivo", - "Africa/Khartoum|Indian/Comoro", - "Africa/Khartoum|Indian/Mayotte", - "Africa/Lagos|Africa/Bangui", - "Africa/Lagos|Africa/Brazzaville", - "Africa/Lagos|Africa/Douala", - "Africa/Lagos|Africa/Kinshasa", - "Africa/Lagos|Africa/Libreville", - "Africa/Lagos|Africa/Luanda", - "Africa/Lagos|Africa/Malabo", - "Africa/Lagos|Africa/Ndjamena", - "Africa/Lagos|Africa/Niamey", - "Africa/Lagos|Africa/Porto-Novo", - "Africa/Maputo|Africa/Blantyre", - "Africa/Maputo|Africa/Bujumbura", - "Africa/Maputo|Africa/Gaborone", - "Africa/Maputo|Africa/Harare", - "Africa/Maputo|Africa/Kigali", - "Africa/Maputo|Africa/Lubumbashi", - "Africa/Maputo|Africa/Lusaka", - "Africa/Tripoli|Libya", - "America/Adak|America/Atka", - "America/Adak|US/Aleutian", - "America/Anchorage|America/Juneau", - "America/Anchorage|America/Nome", - "America/Anchorage|America/Sitka", - "America/Anchorage|America/Yakutat", - "America/Anchorage|US/Alaska", - "America/Argentina/Buenos_Aires|America/Argentina/Catamarca", - "America/Argentina/Buenos_Aires|America/Argentina/ComodRivadavia", - "America/Argentina/Buenos_Aires|America/Argentina/Cordoba", - "America/Argentina/Buenos_Aires|America/Argentina/Jujuy", - "America/Argentina/Buenos_Aires|America/Argentina/La_Rioja", - "America/Argentina/Buenos_Aires|America/Argentina/Mendoza", - "America/Argentina/Buenos_Aires|America/Argentina/Rio_Gallegos", - "America/Argentina/Buenos_Aires|America/Argentina/Salta", - "America/Argentina/Buenos_Aires|America/Argentina/San_Juan", - "America/Argentina/Buenos_Aires|America/Argentina/San_Luis", - "America/Argentina/Buenos_Aires|America/Argentina/Tucuman", - "America/Argentina/Buenos_Aires|America/Argentina/Ushuaia", - "America/Argentina/Buenos_Aires|America/Buenos_Aires", - "America/Argentina/Buenos_Aires|America/Catamarca", - "America/Argentina/Buenos_Aires|America/Cordoba", - "America/Argentina/Buenos_Aires|America/Jujuy", - "America/Argentina/Buenos_Aires|America/Mendoza", - "America/Argentina/Buenos_Aires|America/Rosario", - "America/Campo_Grande|America/Cuiaba", - "America/Chicago|America/Indiana/Knox", - "America/Chicago|America/Indiana/Tell_City", - "America/Chicago|America/Knox_IN", - "America/Chicago|America/Matamoros", - "America/Chicago|America/Menominee", - "America/Chicago|America/North_Dakota/Center", - "America/Chicago|America/North_Dakota/New_Salem", - "America/Chicago|America/Rainy_River", - "America/Chicago|America/Rankin_Inlet", - "America/Chicago|America/Resolute", - "America/Chicago|America/Winnipeg", - "America/Chicago|CST6CDT", - "America/Chicago|Canada/Central", - "America/Chicago|US/Central", - "America/Chicago|US/Indiana-Starke", - "America/Chihuahua|America/Mazatlan", - "America/Chihuahua|Mexico/BajaSur", - "America/Denver|America/Boise", - "America/Denver|America/Cambridge_Bay", - "America/Denver|America/Edmonton", - "America/Denver|America/Inuvik", - "America/Denver|America/Ojinaga", - "America/Denver|America/Shiprock", - "America/Denver|America/Yellowknife", - "America/Denver|Canada/Mountain", - "America/Denver|MST7MDT", - "America/Denver|Navajo", - "America/Denver|US/Mountain", - "America/Fortaleza|America/Belem", - "America/Fortaleza|America/Maceio", - "America/Fortaleza|America/Recife", - "America/Fortaleza|America/Santarem", - "America/Halifax|America/Glace_Bay", - "America/Halifax|America/Moncton", - "America/Halifax|America/Thule", - "America/Halifax|Atlantic/Bermuda", - "America/Halifax|Canada/Atlantic", - "America/Havana|Cuba", - "America/Los_Angeles|America/Dawson", - "America/Los_Angeles|America/Ensenada", - "America/Los_Angeles|America/Santa_Isabel", - "America/Los_Angeles|America/Tijuana", - "America/Los_Angeles|America/Vancouver", - "America/Los_Angeles|America/Whitehorse", - "America/Los_Angeles|Canada/Pacific", - "America/Los_Angeles|Canada/Yukon", - "America/Los_Angeles|Mexico/BajaNorte", - "America/Los_Angeles|PST8PDT", - "America/Los_Angeles|US/Pacific", - "America/Los_Angeles|US/Pacific-New", - "America/Managua|America/Belize", - "America/Managua|America/Costa_Rica", - "America/Managua|America/El_Salvador", - "America/Managua|America/Guatemala", - "America/Managua|America/Regina", - "America/Managua|America/Swift_Current", - "America/Managua|America/Tegucigalpa", - "America/Managua|Canada/East-Saskatchewan", - "America/Managua|Canada/Saskatchewan", - "America/Manaus|America/Boa_Vista", - "America/Manaus|America/Porto_Velho", - "America/Manaus|Brazil/West", - "America/Mexico_City|America/Merida", - "America/Mexico_City|America/Monterrey", - "America/Mexico_City|Mexico/General", - "America/New_York|America/Detroit", - "America/New_York|America/Fort_Wayne", - "America/New_York|America/Indiana/Indianapolis", - "America/New_York|America/Indiana/Marengo", - "America/New_York|America/Indiana/Petersburg", - "America/New_York|America/Indiana/Vevay", - "America/New_York|America/Indiana/Vincennes", - "America/New_York|America/Indiana/Winamac", - "America/New_York|America/Indianapolis", - "America/New_York|America/Iqaluit", - "America/New_York|America/Kentucky/Louisville", - "America/New_York|America/Kentucky/Monticello", - "America/New_York|America/Louisville", - "America/New_York|America/Montreal", - "America/New_York|America/Nassau", - "America/New_York|America/Nipigon", - "America/New_York|America/Pangnirtung", - "America/New_York|America/Thunder_Bay", - "America/New_York|America/Toronto", - "America/New_York|Canada/Eastern", - "America/New_York|EST5EDT", - "America/New_York|US/East-Indiana", - "America/New_York|US/Eastern", - "America/New_York|US/Michigan", - "America/Noronha|Brazil/DeNoronha", - "America/Panama|America/Atikokan", - "America/Panama|America/Cayman", - "America/Panama|America/Coral_Harbour", - "America/Panama|America/Jamaica", - "America/Panama|EST", - "America/Panama|Jamaica", - "America/Phoenix|America/Creston", - "America/Phoenix|America/Dawson_Creek", - "America/Phoenix|America/Hermosillo", - "America/Phoenix|MST", - "America/Phoenix|US/Arizona", - "America/Rio_Branco|America/Eirunepe", - "America/Rio_Branco|America/Porto_Acre", - "America/Rio_Branco|Brazil/Acre", - "America/Santiago|Antarctica/Palmer", - "America/Santiago|Chile/Continental", - "America/Santo_Domingo|America/Anguilla", - "America/Santo_Domingo|America/Antigua", - "America/Santo_Domingo|America/Aruba", - "America/Santo_Domingo|America/Barbados", - "America/Santo_Domingo|America/Blanc-Sablon", - "America/Santo_Domingo|America/Curacao", - "America/Santo_Domingo|America/Dominica", - "America/Santo_Domingo|America/Grenada", - "America/Santo_Domingo|America/Guadeloupe", - "America/Santo_Domingo|America/Kralendijk", - "America/Santo_Domingo|America/Lower_Princes", - "America/Santo_Domingo|America/Marigot", - "America/Santo_Domingo|America/Martinique", - "America/Santo_Domingo|America/Montserrat", - "America/Santo_Domingo|America/Port_of_Spain", - "America/Santo_Domingo|America/Puerto_Rico", - "America/Santo_Domingo|America/St_Barthelemy", - "America/Santo_Domingo|America/St_Kitts", - "America/Santo_Domingo|America/St_Lucia", - "America/Santo_Domingo|America/St_Thomas", - "America/Santo_Domingo|America/St_Vincent", - "America/Santo_Domingo|America/Tortola", - "America/Santo_Domingo|America/Virgin", - "America/Sao_Paulo|Brazil/East", - "America/St_Johns|Canada/Newfoundland", - "Asia/Almaty|Asia/Qyzylorda", - "Asia/Aqtobe|Asia/Aqtau", - "Asia/Aqtobe|Asia/Oral", - "Asia/Ashgabat|Asia/Ashkhabad", - "Asia/Baghdad|Asia/Aden", - "Asia/Baghdad|Asia/Bahrain", - "Asia/Baghdad|Asia/Kuwait", - "Asia/Baghdad|Asia/Qatar", - "Asia/Baghdad|Asia/Riyadh", - "Asia/Bangkok|Asia/Ho_Chi_Minh", - "Asia/Bangkok|Asia/Phnom_Penh", - "Asia/Bangkok|Asia/Saigon", - "Asia/Bangkok|Asia/Vientiane", - "Asia/Dhaka|Asia/Dacca", - "Asia/Dubai|Asia/Muscat", - "Asia/Hong_Kong|Hongkong", - "Asia/Jakarta|Asia/Pontianak", - "Asia/Jerusalem|Asia/Tel_Aviv", - "Asia/Jerusalem|Israel", - "Asia/Kathmandu|Asia/Katmandu", - "Asia/Kolkata|Asia/Calcutta", - "Asia/Kolkata|Asia/Colombo", - "Asia/Kuala_Lumpur|Asia/Kuching", - "Asia/Makassar|Asia/Ujung_Pandang", - "Asia/Seoul|ROK", - "Asia/Shanghai|Asia/Chongqing", - "Asia/Shanghai|Asia/Chungking", - "Asia/Shanghai|Asia/Harbin", - "Asia/Shanghai|Asia/Macao", - "Asia/Shanghai|Asia/Macau", - "Asia/Shanghai|Asia/Taipei", - "Asia/Shanghai|PRC", - "Asia/Shanghai|ROC", - "Asia/Singapore|Singapore", - "Asia/Tashkent|Asia/Samarkand", - "Asia/Tehran|Iran", - "Asia/Thimphu|Asia/Thimbu", - "Asia/Tokyo|Japan", - "Asia/Ulaanbaatar|Asia/Ulan_Bator", - "Asia/Urumqi|Asia/Kashgar", - "Australia/Adelaide|Australia/Broken_Hill", - "Australia/Adelaide|Australia/South", - "Australia/Adelaide|Australia/Yancowinna", - "Australia/Brisbane|Australia/Lindeman", - "Australia/Brisbane|Australia/Queensland", - "Australia/Darwin|Australia/North", - "Australia/Lord_Howe|Australia/LHI", - "Australia/Perth|Australia/West", - "Australia/Sydney|Australia/ACT", - "Australia/Sydney|Australia/Canberra", - "Australia/Sydney|Australia/Currie", - "Australia/Sydney|Australia/Hobart", - "Australia/Sydney|Australia/Melbourne", - "Australia/Sydney|Australia/NSW", - "Australia/Sydney|Australia/Tasmania", - "Australia/Sydney|Australia/Victoria", - "Etc/UCT|UCT", - "Etc/UTC|Etc/Universal", - "Etc/UTC|Etc/Zulu", - "Etc/UTC|UTC", - "Etc/UTC|Universal", - "Etc/UTC|Zulu", - "Europe/Astrakhan|Europe/Ulyanovsk", - "Europe/Athens|Asia/Nicosia", - "Europe/Athens|EET", - "Europe/Athens|Europe/Bucharest", - "Europe/Athens|Europe/Helsinki", - "Europe/Athens|Europe/Kiev", - "Europe/Athens|Europe/Mariehamn", - "Europe/Athens|Europe/Nicosia", - "Europe/Athens|Europe/Riga", - "Europe/Athens|Europe/Sofia", - "Europe/Athens|Europe/Tallinn", - "Europe/Athens|Europe/Uzhgorod", - "Europe/Athens|Europe/Vilnius", - "Europe/Athens|Europe/Zaporozhye", - "Europe/Chisinau|Europe/Tiraspol", - "Europe/Dublin|Eire", - "Europe/Istanbul|Asia/Istanbul", - "Europe/Istanbul|Turkey", - "Europe/Lisbon|Atlantic/Canary", - "Europe/Lisbon|Atlantic/Faeroe", - "Europe/Lisbon|Atlantic/Faroe", - "Europe/Lisbon|Atlantic/Madeira", - "Europe/Lisbon|Portugal", - "Europe/Lisbon|WET", - "Europe/London|Europe/Belfast", - "Europe/London|Europe/Guernsey", - "Europe/London|Europe/Isle_of_Man", - "Europe/London|Europe/Jersey", - "Europe/London|GB", - "Europe/London|GB-Eire", - "Europe/Moscow|Europe/Volgograd", - "Europe/Moscow|W-SU", - "Europe/Paris|Africa/Ceuta", - "Europe/Paris|Arctic/Longyearbyen", - "Europe/Paris|Atlantic/Jan_Mayen", - "Europe/Paris|CET", - "Europe/Paris|Europe/Amsterdam", - "Europe/Paris|Europe/Andorra", - "Europe/Paris|Europe/Belgrade", - "Europe/Paris|Europe/Berlin", - "Europe/Paris|Europe/Bratislava", - "Europe/Paris|Europe/Brussels", - "Europe/Paris|Europe/Budapest", - "Europe/Paris|Europe/Busingen", - "Europe/Paris|Europe/Copenhagen", - "Europe/Paris|Europe/Gibraltar", - "Europe/Paris|Europe/Ljubljana", - "Europe/Paris|Europe/Luxembourg", - "Europe/Paris|Europe/Madrid", - "Europe/Paris|Europe/Malta", - "Europe/Paris|Europe/Monaco", - "Europe/Paris|Europe/Oslo", - "Europe/Paris|Europe/Podgorica", - "Europe/Paris|Europe/Prague", - "Europe/Paris|Europe/Rome", - "Europe/Paris|Europe/San_Marino", - "Europe/Paris|Europe/Sarajevo", - "Europe/Paris|Europe/Skopje", - "Europe/Paris|Europe/Stockholm", - "Europe/Paris|Europe/Tirane", - "Europe/Paris|Europe/Vaduz", - "Europe/Paris|Europe/Vatican", - "Europe/Paris|Europe/Vienna", - "Europe/Paris|Europe/Warsaw", - "Europe/Paris|Europe/Zagreb", - "Europe/Paris|Europe/Zurich", - "Europe/Paris|Poland", - "Pacific/Auckland|Antarctica/McMurdo", - "Pacific/Auckland|Antarctica/South_Pole", - "Pacific/Auckland|NZ", - "Pacific/Chatham|NZ-CHAT", - "Pacific/Chuuk|Pacific/Truk", - "Pacific/Chuuk|Pacific/Yap", - "Pacific/Easter|Chile/EasterIsland", - "Pacific/Guam|Pacific/Saipan", - "Pacific/Honolulu|HST", - "Pacific/Honolulu|Pacific/Johnston", - "Pacific/Honolulu|US/Hawaii", - "Pacific/Majuro|Kwajalein", - "Pacific/Majuro|Pacific/Kwajalein", - "Pacific/Pago_Pago|Pacific/Midway", - "Pacific/Pago_Pago|Pacific/Samoa", - "Pacific/Pago_Pago|US/Samoa", - "Pacific/Pohnpei|Pacific/Ponape" - ] - }); - - - return moment; -})); diff --git a/static/moment.js b/static/moment.js deleted file mode 100644 index 714b4c44..00000000 --- a/static/moment.js +++ /dev/null @@ -1,4195 +0,0 @@ -//! moment.js -//! version : 2.14.1 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - -;(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - global.moment = factory() -}(this, function () { 'use strict'; - - var hookCallback; - - function utils_hooks__hooks () { - return hookCallback.apply(null, arguments); - } - - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback (callback) { - hookCallback = callback; - } - - function isArray(input) { - return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; - } - - function isObject(input) { - return Object.prototype.toString.call(input) === '[object Object]'; - } - - function isObjectEmpty(obj) { - var k; - for (k in obj) { - // even if its not own property I'd still call it non-empty - return false; - } - return true; - } - - function isDate(input) { - return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; - } - - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } - - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); - } - - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } - - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function create_utc__createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); - } - - function defaultParsingFlags() { - // We need to deep clone this object. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso : false, - parsedDateParts : [], - meridiem : null - }; - } - - function getParsingFlags(m) { - if (m._pf == null) { - m._pf = defaultParsingFlags(); - } - return m._pf; - } - - var some; - if (Array.prototype.some) { - some = Array.prototype.some; - } else { - some = function (fun) { - var t = Object(this); - var len = t.length >>> 0; - - for (var i = 0; i < len; i++) { - if (i in t && fun.call(this, t[i], i, t)) { - return true; - } - } - - return false; - }; - } - - function valid__isValid(m) { - if (m._isValid == null) { - var flags = getParsingFlags(m); - var parsedParts = some.call(flags.parsedDateParts, function (i) { - return i != null; - }); - m._isValid = !isNaN(m._d.getTime()) && - flags.overflow < 0 && - !flags.empty && - !flags.invalidMonth && - !flags.invalidWeekday && - !flags.nullInput && - !flags.invalidFormat && - !flags.userInvalidated && - (!flags.meridiem || (flags.meridiem && parsedParts)); - - if (m._strict) { - m._isValid = m._isValid && - flags.charsLeftOver === 0 && - flags.unusedTokens.length === 0 && - flags.bigHour === undefined; - } - } - return m._isValid; - } - - function valid__createInvalid (flags) { - var m = create_utc__createUTC(NaN); - if (flags != null) { - extend(getParsingFlags(m), flags); - } - else { - getParsingFlags(m).userInvalidated = true; - } - - return m; - } - - function isUndefined(input) { - return input === void 0; - } - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - var momentProperties = utils_hooks__hooks.momentProperties = []; - - function copyConfig(to, from) { - var i, prop, val; - - if (!isUndefined(from._isAMomentObject)) { - to._isAMomentObject = from._isAMomentObject; - } - if (!isUndefined(from._i)) { - to._i = from._i; - } - if (!isUndefined(from._f)) { - to._f = from._f; - } - if (!isUndefined(from._l)) { - to._l = from._l; - } - if (!isUndefined(from._strict)) { - to._strict = from._strict; - } - if (!isUndefined(from._tzm)) { - to._tzm = from._tzm; - } - if (!isUndefined(from._isUTC)) { - to._isUTC = from._isUTC; - } - if (!isUndefined(from._offset)) { - to._offset = from._offset; - } - if (!isUndefined(from._pf)) { - to._pf = getParsingFlags(from); - } - if (!isUndefined(from._locale)) { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (!isUndefined(val)) { - to[prop] = val; - } - } - } - - return to; - } - - var updateInProgress = false; - - // Moment prototype object - function Moment(config) { - copyConfig(this, config); - this._d = new Date(config._d != null ? config._d.getTime() : NaN); - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - utils_hooks__hooks.updateOffset(this); - updateInProgress = false; - } - } - - function isMoment (obj) { - return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); - } - - function absFloor (number) { - if (number < 0) { - // -0 -> 0 - return Math.ceil(number) || 0; - } else { - return Math.floor(number); - } - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - value = absFloor(coercedNumber); - } - - return value; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function warn(msg) { - if (utils_hooks__hooks.suppressDeprecationWarnings === false && - (typeof console !== 'undefined') && console.warn) { - console.warn('Deprecation warning: ' + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - - return extend(function () { - if (utils_hooks__hooks.deprecationHandler != null) { - utils_hooks__hooks.deprecationHandler(null, msg); - } - if (firstTime) { - warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - var deprecations = {}; - - function deprecateSimple(name, msg) { - if (utils_hooks__hooks.deprecationHandler != null) { - utils_hooks__hooks.deprecationHandler(name, msg); - } - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; - } - } - - utils_hooks__hooks.suppressDeprecationWarnings = false; - utils_hooks__hooks.deprecationHandler = null; - - function isFunction(input) { - return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; - } - - function locale_set__set (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (isFunction(prop)) { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - this._config = config; - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _ordinalParseLenient. - this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); - } - - function mergeConfigs(parentConfig, childConfig) { - var res = extend({}, parentConfig), prop; - for (prop in childConfig) { - if (hasOwnProp(childConfig, prop)) { - if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { - res[prop] = {}; - extend(res[prop], parentConfig[prop]); - extend(res[prop], childConfig[prop]); - } else if (childConfig[prop] != null) { - res[prop] = childConfig[prop]; - } else { - delete res[prop]; - } - } - } - for (prop in parentConfig) { - if (hasOwnProp(parentConfig, prop) && - !hasOwnProp(childConfig, prop) && - isObject(parentConfig[prop])) { - // make sure changes to properties don't modify parent config - res[prop] = extend({}, res[prop]); - } - } - return res; - } - - function Locale(config) { - if (config != null) { - this.set(config); - } - } - - var keys; - - if (Object.keys) { - keys = Object.keys; - } else { - keys = function (obj) { - var i, res = []; - for (i in obj) { - if (hasOwnProp(obj, i)) { - res.push(i); - } - } - return res; - }; - } - - var defaultCalendar = { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }; - - function locale_calendar__calendar (key, mom, now) { - var output = this._calendar[key] || this._calendar['sameElse']; - return isFunction(output) ? output.call(mom, now) : output; - } - - var defaultLongDateFormat = { - LTS : 'h:mm:ss A', - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY h:mm A', - LLLL : 'dddd, MMMM D, YYYY h:mm A' - }; - - function longDateFormat (key) { - var format = this._longDateFormat[key], - formatUpper = this._longDateFormat[key.toUpperCase()]; - - if (format || !formatUpper) { - return format; - } - - this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - - return this._longDateFormat[key]; - } - - var defaultInvalidDate = 'Invalid date'; - - function invalidDate () { - return this._invalidDate; - } - - var defaultOrdinal = '%d'; - var defaultOrdinalParse = /\d{1,2}/; - - function ordinal (number) { - return this._ordinal.replace('%d', number); - } - - var defaultRelativeTime = { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }; - - function relative__relativeTime (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (isFunction(output)) ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - } - - function pastFuture (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return isFunction(format) ? format(output) : format.replace(/%s/i, output); - } - - var aliases = {}; - - function addUnitAlias (unit, shorthand) { - var lowerCase = unit.toLowerCase(); - aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; - } - - function normalizeUnits(units) { - return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - var priorities = {}; - - function addUnitPriority(unit, priority) { - priorities[unit] = priority; - } - - function getPrioritizedUnits(unitsObj) { - var units = []; - for (var u in unitsObj) { - units.push({unit: u, priority: priorities[u]}); - } - units.sort(function (a, b) { - return a.priority - b.priority; - }); - return units; - } - - function makeGetSet (unit, keepTime) { - return function (value) { - if (value != null) { - get_set__set(this, unit, value); - utils_hooks__hooks.updateOffset(this, keepTime); - return this; - } else { - return get_set__get(this, unit); - } - }; - } - - function get_set__get (mom, unit) { - return mom.isValid() ? - mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; - } - - function get_set__set (mom, unit, value) { - if (mom.isValid()) { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - - // MOMENTS - - function stringGet (units) { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](); - } - return this; - } - - - function stringSet (units, value) { - if (typeof units === 'object') { - units = normalizeObjectUnits(units); - var prioritized = getPrioritizedUnits(units); - for (var i = 0; i < prioritized.length; i++) { - this[prioritized[i].unit](units[prioritized[i].unit]); - } - } else { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](value); - } - } - return this; - } - - function zeroFill(number, targetLength, forceSign) { - var absNumber = '' + Math.abs(number), - zerosToFill = targetLength - absNumber.length, - sign = number >= 0; - return (sign ? (forceSign ? '+' : '') : '-') + - Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; - } - - var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - - var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - - var formatFunctions = {}; - - var formatTokenFunctions = {}; - - // token: 'M' - // padded: ['MM', 2] - // ordinal: 'Mo' - // callback: function () { this.month() + 1 } - function addFormatToken (token, padded, ordinal, callback) { - var func = callback; - if (typeof callback === 'string') { - func = function () { - return this[callback](); - }; - } - if (token) { - formatTokenFunctions[token] = func; - } - if (padded) { - formatTokenFunctions[padded[0]] = function () { - return zeroFill(func.apply(this, arguments), padded[1], padded[2]); - }; - } - if (ordinal) { - formatTokenFunctions[ordinal] = function () { - return this.localeData().ordinal(func.apply(this, arguments), token); - }; - } - } - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = '', i; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - var match1 = /\d/; // 0 - 9 - var match2 = /\d\d/; // 00 - 99 - var match3 = /\d{3}/; // 000 - 999 - var match4 = /\d{4}/; // 0000 - 9999 - var match6 = /[+-]?\d{6}/; // -999999 - 999999 - var match1to2 = /\d\d?/; // 0 - 99 - var match3to4 = /\d\d\d\d?/; // 999 - 9999 - var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 - var match1to3 = /\d{1,3}/; // 0 - 999 - var match1to4 = /\d{1,4}/; // 0 - 9999 - var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 - - var matchUnsigned = /\d+/; // 0 - inf - var matchSigned = /[+-]?\d+/; // -inf - inf - - var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z - var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z - - var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 - - // any word (or two) characters or numbers including two/three word month in arabic. - // includes scottish gaelic two word and hyphenated months - var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; - - - var regexes = {}; - - function addRegexToken (token, regex, strictRegex) { - regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { - return (isStrict && strictRegex) ? strictRegex : regex; - }; - } - - function getParseRegexForToken (token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } - - return regexes[token](config._strict, config._locale); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function unescapeFormat(s) { - return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - })); - } - - function regexEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - var tokens = {}; - - function addParseToken (token, callback) { - var i, func = callback; - if (typeof token === 'string') { - token = [token]; - } - if (typeof callback === 'number') { - func = function (input, array) { - array[callback] = toInt(input); - }; - } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; - } - } - - function addWeekParseToken (token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); - } - - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); - } - } - - var YEAR = 0; - var MONTH = 1; - var DATE = 2; - var HOUR = 3; - var MINUTE = 4; - var SECOND = 5; - var MILLISECOND = 6; - var WEEK = 7; - var WEEKDAY = 8; - - var indexOf; - - if (Array.prototype.indexOf) { - indexOf = Array.prototype.indexOf; - } else { - indexOf = function (o) { - // I know - var i; - for (i = 0; i < this.length; ++i) { - if (this[i] === o) { - return i; - } - } - return -1; - }; - } - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - // FORMATTING - - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); - - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); - - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); - - // ALIASES - - addUnitAlias('month', 'M'); - - // PRIORITY - - addUnitPriority('month', 8); - - // PARSING - - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', function (isStrict, locale) { - return locale.monthsShortRegex(isStrict); - }); - addRegexToken('MMMM', function (isStrict, locale) { - return locale.monthsRegex(isStrict); - }); - - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); - - addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { - var month = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (month != null) { - array[MONTH] = month; - } else { - getParsingFlags(config).invalidMonth = input; - } - }); - - // LOCALES - - var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/; - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); - function localeMonths (m, format) { - return isArray(this._months) ? this._months[m.month()] : - this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; - } - - var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); - function localeMonthsShort (m, format) { - return isArray(this._monthsShort) ? this._monthsShort[m.month()] : - this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; - } - - function units_month__handleStrictParse(monthName, format, strict) { - var i, ii, mom, llc = monthName.toLocaleLowerCase(); - if (!this._monthsParse) { - // this is not used - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - for (i = 0; i < 12; ++i) { - mom = create_utc__createUTC([2000, i]); - this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); - this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeMonthsParse (monthName, format, strict) { - var i, mom, regex; - - if (this._monthsParseExact) { - return units_month__handleStrictParse.call(this, monthName, format, strict); - } - - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } - - // TODO: add sorting - // Sorting makes sure if one month (or abbr) is a prefix of another - // see sorting in computeMonthsParse - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); - this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); - } - if (!strict && !this._monthsParse[i]) { - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { - return i; - } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } - } - } - - // MOMENTS - - function setMonth (mom, value) { - var dayOfMonth; - - if (!mom.isValid()) { - // No op - return mom; - } - - if (typeof value === 'string') { - if (/^\d+$/.test(value)) { - value = toInt(value); - } else { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - } - - dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function getSetMonth (value) { - if (value != null) { - setMonth(this, value); - utils_hooks__hooks.updateOffset(this, true); - return this; - } else { - return get_set__get(this, 'Month'); - } - } - - function getDaysInMonth () { - return daysInMonth(this.year(), this.month()); - } - - var defaultMonthsShortRegex = matchWord; - function monthsShortRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsShortStrictRegex; - } else { - return this._monthsShortRegex; - } - } else { - if (!hasOwnProp(this, '_monthsShortRegex')) { - this._monthsShortRegex = defaultMonthsShortRegex; - } - return this._monthsShortStrictRegex && isStrict ? - this._monthsShortStrictRegex : this._monthsShortRegex; - } - } - - var defaultMonthsRegex = matchWord; - function monthsRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsStrictRegex; - } else { - return this._monthsRegex; - } - } else { - if (!hasOwnProp(this, '_monthsRegex')) { - this._monthsRegex = defaultMonthsRegex; - } - return this._monthsStrictRegex && isStrict ? - this._monthsStrictRegex : this._monthsRegex; - } - } - - function computeMonthsParse () { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var shortPieces = [], longPieces = [], mixedPieces = [], - i, mom; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, i]); - shortPieces.push(this.monthsShort(mom, '')); - longPieces.push(this.months(mom, '')); - mixedPieces.push(this.months(mom, '')); - mixedPieces.push(this.monthsShort(mom, '')); - } - // Sorting makes sure if one month (or abbr) is a prefix of another it - // will match the longer piece. - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 12; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - } - for (i = 0; i < 24; i++) { - mixedPieces[i] = regexEscape(mixedPieces[i]); - } - - this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._monthsShortRegex = this._monthsRegex; - this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); - } - - // FORMATTING - - addFormatToken('Y', 0, 0, function () { - var y = this.year(); - return y <= 9999 ? '' + y : '+' + y; - }); - - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); - - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - - // ALIASES - - addUnitAlias('year', 'y'); - - // PRIORITIES - - addUnitPriority('year', 1); - - // PARSING - - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); - - addParseToken(['YYYYY', 'YYYYYY'], YEAR); - addParseToken('YYYY', function (input, array) { - array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); - }); - addParseToken('YY', function (input, array) { - array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); - }); - addParseToken('Y', function (input, array) { - array[YEAR] = parseInt(input, 10); - }); - - // HELPERS - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - // HOOKS - - utils_hooks__hooks.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - // MOMENTS - - var getSetYear = makeGetSet('FullYear', true); - - function getIsLeapYear () { - return isLeapYear(this.year()); - } - - function createDate (y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { - date.setFullYear(y); - } - return date; - } - - function createUTCDate (y) { - var date = new Date(Date.UTC.apply(null, arguments)); - - //the Date.UTC function remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { - date.setUTCFullYear(y); - } - return date; - } - - // start-of-first-week - start-of-year - function firstWeekOffset(year, dow, doy) { - var // first-week day -- which january is always in the first week (4 for iso, 1 for other) - fwd = 7 + dow - doy, - // first-week day local weekday -- which local weekday is fwd - fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - - return -fwdlw + fwd - 1; - } - - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, dow, doy) { - var localWeekday = (7 + weekday - dow) % 7, - weekOffset = firstWeekOffset(year, dow, doy), - dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, - resYear, resDayOfYear; - - if (dayOfYear <= 0) { - resYear = year - 1; - resDayOfYear = daysInYear(resYear) + dayOfYear; - } else if (dayOfYear > daysInYear(year)) { - resYear = year + 1; - resDayOfYear = dayOfYear - daysInYear(year); - } else { - resYear = year; - resDayOfYear = dayOfYear; - } - - return { - year: resYear, - dayOfYear: resDayOfYear - }; - } - - function weekOfYear(mom, dow, doy) { - var weekOffset = firstWeekOffset(mom.year(), dow, doy), - week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, - resWeek, resYear; - - if (week < 1) { - resYear = mom.year() - 1; - resWeek = week + weeksInYear(resYear, dow, doy); - } else if (week > weeksInYear(mom.year(), dow, doy)) { - resWeek = week - weeksInYear(mom.year(), dow, doy); - resYear = mom.year() + 1; - } else { - resYear = mom.year(); - resWeek = week; - } - - return { - week: resWeek, - year: resYear - }; - } - - function weeksInYear(year, dow, doy) { - var weekOffset = firstWeekOffset(year, dow, doy), - weekOffsetNext = firstWeekOffset(year + 1, dow, doy); - return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; - } - - // FORMATTING - - addFormatToken('w', ['ww', 2], 'wo', 'week'); - addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - - // ALIASES - - addUnitAlias('week', 'w'); - addUnitAlias('isoWeek', 'W'); - - // PRIORITIES - - addUnitPriority('week', 5); - addUnitPriority('isoWeek', 5); - - // PARSING - - addRegexToken('w', match1to2); - addRegexToken('ww', match1to2, match2); - addRegexToken('W', match1to2); - addRegexToken('WW', match1to2, match2); - - addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { - week[token.substr(0, 1)] = toInt(input); - }); - - // HELPERS - - // LOCALES - - function localeWeek (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - } - - var defaultLocaleWeek = { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }; - - function localeFirstDayOfWeek () { - return this._week.dow; - } - - function localeFirstDayOfYear () { - return this._week.doy; - } - - // MOMENTS - - function getSetWeek (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - function getSetISOWeek (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - // FORMATTING - - addFormatToken('d', 0, 'do', 'day'); - - addFormatToken('dd', 0, 0, function (format) { - return this.localeData().weekdaysMin(this, format); - }); - - addFormatToken('ddd', 0, 0, function (format) { - return this.localeData().weekdaysShort(this, format); - }); - - addFormatToken('dddd', 0, 0, function (format) { - return this.localeData().weekdays(this, format); - }); - - addFormatToken('e', 0, 0, 'weekday'); - addFormatToken('E', 0, 0, 'isoWeekday'); - - // ALIASES - - addUnitAlias('day', 'd'); - addUnitAlias('weekday', 'e'); - addUnitAlias('isoWeekday', 'E'); - - // PRIORITY - addUnitPriority('day', 11); - addUnitPriority('weekday', 11); - addUnitPriority('isoWeekday', 11); - - // PARSING - - addRegexToken('d', match1to2); - addRegexToken('e', match1to2); - addRegexToken('E', match1to2); - addRegexToken('dd', function (isStrict, locale) { - return locale.weekdaysMinRegex(isStrict); - }); - addRegexToken('ddd', function (isStrict, locale) { - return locale.weekdaysShortRegex(isStrict); - }); - addRegexToken('dddd', function (isStrict, locale) { - return locale.weekdaysRegex(isStrict); - }); - - addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { - var weekday = config._locale.weekdaysParse(input, token, config._strict); - // if we didn't get a weekday name, mark the date as invalid - if (weekday != null) { - week.d = weekday; - } else { - getParsingFlags(config).invalidWeekday = input; - } - }); - - addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { - week[token] = toInt(input); - }); - - // HELPERS - - function parseWeekday(input, locale) { - if (typeof input !== 'string') { - return input; - } - - if (!isNaN(input)) { - return parseInt(input, 10); - } - - input = locale.weekdaysParse(input); - if (typeof input === 'number') { - return input; - } - - return null; - } - - function parseIsoWeekday(input, locale) { - if (typeof input === 'string') { - return locale.weekdaysParse(input) % 7 || 7; - } - return isNaN(input) ? null : input; - } - - // LOCALES - - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); - function localeWeekdays (m, format) { - return isArray(this._weekdays) ? this._weekdays[m.day()] : - this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; - } - - var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); - function localeWeekdaysShort (m) { - return this._weekdaysShort[m.day()]; - } - - var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); - function localeWeekdaysMin (m) { - return this._weekdaysMin[m.day()]; - } - - function day_of_week__handleStrictParse(weekdayName, format, strict) { - var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._shortWeekdaysParse = []; - this._minWeekdaysParse = []; - - for (i = 0; i < 7; ++i) { - mom = create_utc__createUTC([2000, 1]).day(i); - this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); - this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); - this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeWeekdaysParse (weekdayName, format, strict) { - var i, mom, regex; - - if (this._weekdaysParseExact) { - return day_of_week__handleStrictParse.call(this, weekdayName, format, strict); - } - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._minWeekdaysParse = []; - this._shortWeekdaysParse = []; - this._fullWeekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - - mom = create_utc__createUTC([2000, 1]).day(i); - if (strict && !this._fullWeekdaysParse[i]) { - this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); - this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); - this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); - } - if (!this._weekdaysParse[i]) { - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - } - - // MOMENTS - - function getSetDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - } - - function getSetLocaleDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - } - - function getSetISODayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - - if (input != null) { - var weekday = parseIsoWeekday(input, this.localeData()); - return this.day(this.day() % 7 ? weekday : weekday - 7); - } else { - return this.day() || 7; - } - } - - var defaultWeekdaysRegex = matchWord; - function weekdaysRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysStrictRegex; - } else { - return this._weekdaysRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysRegex')) { - this._weekdaysRegex = defaultWeekdaysRegex; - } - return this._weekdaysStrictRegex && isStrict ? - this._weekdaysStrictRegex : this._weekdaysRegex; - } - } - - var defaultWeekdaysShortRegex = matchWord; - function weekdaysShortRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysShortStrictRegex; - } else { - return this._weekdaysShortRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysShortRegex')) { - this._weekdaysShortRegex = defaultWeekdaysShortRegex; - } - return this._weekdaysShortStrictRegex && isStrict ? - this._weekdaysShortStrictRegex : this._weekdaysShortRegex; - } - } - - var defaultWeekdaysMinRegex = matchWord; - function weekdaysMinRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysMinStrictRegex; - } else { - return this._weekdaysMinRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysMinRegex')) { - this._weekdaysMinRegex = defaultWeekdaysMinRegex; - } - return this._weekdaysMinStrictRegex && isStrict ? - this._weekdaysMinStrictRegex : this._weekdaysMinRegex; - } - } - - - function computeWeekdaysParse () { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], - i, mom, minp, shortp, longp; - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, 1]).day(i); - minp = this.weekdaysMin(mom, ''); - shortp = this.weekdaysShort(mom, ''); - longp = this.weekdays(mom, ''); - minPieces.push(minp); - shortPieces.push(shortp); - longPieces.push(longp); - mixedPieces.push(minp); - mixedPieces.push(shortp); - mixedPieces.push(longp); - } - // Sorting makes sure if one weekday (or abbr) is a prefix of another it - // will match the longer piece. - minPieces.sort(cmpLenRev); - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 7; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - mixedPieces[i] = regexEscape(mixedPieces[i]); - } - - this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._weekdaysShortRegex = this._weekdaysRegex; - this._weekdaysMinRegex = this._weekdaysRegex; - - this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); - this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); - } - - // FORMATTING - - function hFormat() { - return this.hours() % 12 || 12; - } - - function kFormat() { - return this.hours() || 24; - } - - addFormatToken('H', ['HH', 2], 0, 'hour'); - addFormatToken('h', ['hh', 2], 0, hFormat); - addFormatToken('k', ['kk', 2], 0, kFormat); - - addFormatToken('hmm', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); - }); - - addFormatToken('hmmss', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); - }); - - addFormatToken('Hmm', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2); - }); - - addFormatToken('Hmmss', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); - }); - - function meridiem (token, lowercase) { - addFormatToken(token, 0, 0, function () { - return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); - }); - } - - meridiem('a', true); - meridiem('A', false); - - // ALIASES - - addUnitAlias('hour', 'h'); - - // PRIORITY - addUnitPriority('hour', 13); - - // PARSING - - function matchMeridiem (isStrict, locale) { - return locale._meridiemParse; - } - - addRegexToken('a', matchMeridiem); - addRegexToken('A', matchMeridiem); - addRegexToken('H', match1to2); - addRegexToken('h', match1to2); - addRegexToken('HH', match1to2, match2); - addRegexToken('hh', match1to2, match2); - - addRegexToken('hmm', match3to4); - addRegexToken('hmmss', match5to6); - addRegexToken('Hmm', match3to4); - addRegexToken('Hmmss', match5to6); - - addParseToken(['H', 'HH'], HOUR); - addParseToken(['a', 'A'], function (input, array, config) { - config._isPm = config._locale.isPM(input); - config._meridiem = input; - }); - addParseToken(['h', 'hh'], function (input, array, config) { - array[HOUR] = toInt(input); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('Hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - }); - addParseToken('Hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - }); - - // LOCALES - - function localeIsPM (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - } - - var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; - function localeMeridiem (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - } - - - // MOMENTS - - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - var getSetHour = makeGetSet('Hours', true); - - var baseConfig = { - calendar: defaultCalendar, - longDateFormat: defaultLongDateFormat, - invalidDate: defaultInvalidDate, - ordinal: defaultOrdinal, - ordinalParse: defaultOrdinalParse, - relativeTime: defaultRelativeTime, - - months: defaultLocaleMonths, - monthsShort: defaultLocaleMonthsShort, - - week: defaultLocaleWeek, - - weekdays: defaultLocaleWeekdays, - weekdaysMin: defaultLocaleWeekdaysMin, - weekdaysShort: defaultLocaleWeekdaysShort, - - meridiemParse: defaultLocaleMeridiemParse - }; - - // internal storage for locale config files - var locales = {}; - var globalLocale; - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return null; - } - - function loadLocale(name) { - var oldLocale = null; - // TODO: Find a better way to register and load all the locales in Node - if (!locales[name] && (typeof module !== 'undefined') && - module && module.exports) { - try { - oldLocale = globalLocale._abbr; - require('./locale/' + name); - // because defineLocale currently also sets the global locale, we - // want to undo that for lazy loaded locales - locale_locales__getSetGlobalLocale(oldLocale); - } catch (e) { } - } - return locales[name]; - } - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - function locale_locales__getSetGlobalLocale (key, values) { - var data; - if (key) { - if (isUndefined(values)) { - data = locale_locales__getLocale(key); - } - else { - data = defineLocale(key, values); - } - - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } - } - - return globalLocale._abbr; - } - - function defineLocale (name, config) { - if (config !== null) { - var parentConfig = baseConfig; - config.abbr = name; - if (locales[name] != null) { - deprecateSimple('defineLocaleOverride', - 'use moment.updateLocale(localeName, config) to change ' + - 'an existing locale. moment.defineLocale(localeName, ' + - 'config) should only be used for creating a new locale ' + - 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); - parentConfig = locales[name]._config; - } else if (config.parentLocale != null) { - if (locales[config.parentLocale] != null) { - parentConfig = locales[config.parentLocale]._config; - } else { - // treat as if there is no base config - deprecateSimple('parentLocaleUndefined', - 'specified parentLocale is not defined yet. See http://momentjs.com/guides/#/warnings/parent-locale/'); - } - } - locales[name] = new Locale(mergeConfigs(parentConfig, config)); - - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - } - - function updateLocale(name, config) { - if (config != null) { - var locale, parentConfig = baseConfig; - // MERGE - if (locales[name] != null) { - parentConfig = locales[name]._config; - } - config = mergeConfigs(parentConfig, config); - locale = new Locale(config); - locale.parentLocale = locales[name]; - locales[name] = locale; - - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); - } else { - // pass null for config to unupdate, useful for tests - if (locales[name] != null) { - if (locales[name].parentLocale != null) { - locales[name] = locales[name].parentLocale; - } else if (locales[name] != null) { - delete locales[name]; - } - } - } - return locales[name]; - } - - // returns locale data - function locale_locales__getLocale (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return globalLocale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - } - - function locale_locales__listLocales() { - return keys(locales); - } - - function checkOverflow (m) { - var overflow; - var a = m._a; - - if (a && getParsingFlags(m).overflow === -2) { - overflow = - a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : - a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : - a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : - a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : - a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : - a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - if (getParsingFlags(m)._overflowWeeks && overflow === -1) { - overflow = WEEK; - } - if (getParsingFlags(m)._overflowWeekday && overflow === -1) { - overflow = WEEKDAY; - } - - getParsingFlags(m).overflow = overflow; - } - - return m; - } - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; - var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; - - var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; - - var isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], - ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], - ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], - ['GGGG-[W]WW', /\d{4}-W\d\d/, false], - ['YYYY-DDD', /\d{4}-\d{3}/], - ['YYYY-MM', /\d{4}-\d\d/, false], - ['YYYYYYMMDD', /[+-]\d{10}/], - ['YYYYMMDD', /\d{8}/], - // YYYYMM is NOT allowed by the standard - ['GGGG[W]WWE', /\d{4}W\d{3}/], - ['GGGG[W]WW', /\d{4}W\d{2}/, false], - ['YYYYDDD', /\d{7}/] - ]; - - // iso time formats and regexes - var isoTimes = [ - ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], - ['HH:mm:ss', /\d\d:\d\d:\d\d/], - ['HH:mm', /\d\d:\d\d/], - ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], - ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], - ['HHmmss', /\d\d\d\d\d\d/], - ['HHmm', /\d\d\d\d/], - ['HH', /\d\d/] - ]; - - var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - - // date from iso format - function configFromISO(config) { - var i, l, - string = config._i, - match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), - allowTime, dateFormat, timeFormat, tzFormat; - - if (match) { - getParsingFlags(config).iso = true; - - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(match[1])) { - dateFormat = isoDates[i][0]; - allowTime = isoDates[i][2] !== false; - break; - } - } - if (dateFormat == null) { - config._isValid = false; - return; - } - if (match[3]) { - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(match[3])) { - // match[2] should be 'T' or space - timeFormat = (match[2] || ' ') + isoTimes[i][0]; - break; - } - } - if (timeFormat == null) { - config._isValid = false; - return; - } - } - if (!allowTime && timeFormat != null) { - config._isValid = false; - return; - } - if (match[4]) { - if (tzRegex.exec(match[4])) { - tzFormat = 'Z'; - } else { - config._isValid = false; - return; - } - } - config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); - configFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - // date from iso format or fallback - function configFromString(config) { - var matched = aspNetJsonRegex.exec(config._i); - - if (matched !== null) { - config._d = new Date(+matched[1]); - return; - } - - configFromISO(config); - if (config._isValid === false) { - delete config._isValid; - utils_hooks__hooks.createFromInputFallback(config); - } - } - - utils_hooks__hooks.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); - - // Pick the first defined of two or three arguments. - function defaults(a, b, c) { - if (a != null) { - return a; - } - if (b != null) { - return b; - } - return c; - } - - function currentDateArray(config) { - // hooks is actually the exported moment object - var nowValue = new Date(utils_hooks__hooks.now()); - if (config._useUTC) { - return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; - } - return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function configFromArray (config) { - var i, date, input = [], currentDate, yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - - if (config._dayOfYear > daysInYear(yearToUse)) { - getParsingFlags(config)._overflowDayOfYear = true; - } - - date = createUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - // Check for 24:00:00.000 - if (config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0) { - config._nextDay = true; - config._a[HOUR] = 0; - } - - config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } - - if (config._nextDay) { - config._a[HOUR] = 24; - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); - week = defaults(w.W, 1); - weekday = defaults(w.E, 1); - if (weekday < 1 || weekday > 7) { - weekdayOverflow = true; - } - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); - week = defaults(w.w, 1); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < 0 || weekday > 6) { - weekdayOverflow = true; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - if (w.e < 0 || w.e > 6) { - weekdayOverflow = true; - } - } else { - // default to begining of week - weekday = dow; - } - } - if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { - getParsingFlags(config)._overflowWeeks = true; - } else if (weekdayOverflow != null) { - getParsingFlags(config)._overflowWeekday = true; - } else { - temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - } - - // constant that refers to the ISO standard - utils_hooks__hooks.ISO_8601 = function () {}; - - // date from string and format string - function configFromStringAndFormat(config) { - // TODO: Move this to another part of the creation flow to prevent circular deps - if (config._f === utils_hooks__hooks.ISO_8601) { - configFromISO(config); - return; - } - - config._a = []; - getParsingFlags(config).empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - // console.log('token', token, 'parsedInput', parsedInput, - // 'regex', getParseRegexForToken(token, config)); - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - getParsingFlags(config).unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - getParsingFlags(config).empty = false; - } - else { - getParsingFlags(config).unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - getParsingFlags(config).unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - getParsingFlags(config).unusedInput.push(string); - } - - // clear _12h flag if hour is <= 12 - if (config._a[HOUR] <= 12 && - getParsingFlags(config).bigHour === true && - config._a[HOUR] > 0) { - getParsingFlags(config).bigHour = undefined; - } - - getParsingFlags(config).parsedDateParts = config._a.slice(0); - getParsingFlags(config).meridiem = config._meridiem; - // handle meridiem - config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - - configFromArray(config); - checkOverflow(config); - } - - - function meridiemFixWrap (locale, hour, meridiem) { - var isPm; - - if (meridiem == null) { - // nothing to do - return hour; - } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; - } else { - // this is not supposed to happen - return hour; - } - } - - // date from string and array of format strings - function configFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - getParsingFlags(config).invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._f = config._f[i]; - configFromStringAndFormat(tempConfig); - - if (!valid__isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += getParsingFlags(tempConfig).charsLeftOver; - - //or tokens - currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - - getParsingFlags(tempConfig).score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - function configFromObject(config) { - if (config._d) { - return; - } - - var i = normalizeObjectUnits(config._i); - config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { - return obj && parseInt(obj, 10); - }); - - configFromArray(config); - } - - function createFromConfig (config) { - var res = new Moment(checkOverflow(prepareConfig(config))); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } - - return res; - } - - function prepareConfig (config) { - var input = config._i, - format = config._f; - - config._locale = config._locale || locale_locales__getLocale(config._l); - - if (input === null || (format === undefined && input === '')) { - return valid__createInvalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (isMoment(input)) { - return new Moment(checkOverflow(input)); - } else if (isArray(format)) { - configFromStringAndArray(config); - } else if (isDate(input)) { - config._d = input; - } else if (format) { - configFromStringAndFormat(config); - } else { - configFromInput(config); - } - - if (!valid__isValid(config)) { - config._d = null; - } - - return config; - } - - function configFromInput(config) { - var input = config._i; - if (input === undefined) { - config._d = new Date(utils_hooks__hooks.now()); - } else if (isDate(input)) { - config._d = new Date(input.valueOf()); - } else if (typeof input === 'string') { - configFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - configFromArray(config); - } else if (typeof(input) === 'object') { - configFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - utils_hooks__hooks.createFromInputFallback(config); - } - } - - function createLocalOrUTC (input, format, locale, strict, isUTC) { - var c = {}; - - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - - if ((isObject(input) && isObjectEmpty(input)) || - (isArray(input) && input.length === 0)) { - input = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - - return createFromConfig(c); - } - - function local__createLocal (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); - } - - var prototypeMin = deprecate( - 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = local__createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other < this ? this : other; - } else { - return valid__createInvalid(); - } - } - ); - - var prototypeMax = deprecate( - 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = local__createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other > this ? this : other; - } else { - return valid__createInvalid(); - } - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return local__createLocal(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (!moments[i].isValid() || moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - // TODO: Use [].sort instead? - function min () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - } - - function max () { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - } - - var now = function () { - return Date.now ? Date.now() : +(new Date()); - }; - - function Duration (duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._locale = locale_locales__getLocale(); - - this._bubble(); - } - - function isDuration (obj) { - return obj instanceof Duration; - } - - // FORMATTING - - function offset (token, separator) { - addFormatToken(token, 0, 0, function () { - var offset = this.utcOffset(); - var sign = '+'; - if (offset < 0) { - offset = -offset; - sign = '-'; - } - return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); - }); - } - - offset('Z', ':'); - offset('ZZ', ''); - - // PARSING - - addRegexToken('Z', matchShortOffset); - addRegexToken('ZZ', matchShortOffset); - addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(matchShortOffset, input); - }); - - // HELPERS - - // timezone chunker - // '+10:00' > ['10', '00'] - // '-1530' > ['-15', '30'] - var chunkOffset = /([\+\-]|\d\d)/gi; - - function offsetFromString(matcher, string) { - var matches = ((string || '').match(matcher) || []); - var chunk = matches[matches.length - 1] || []; - var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; - var minutes = +(parts[1] * 60) + toInt(parts[2]); - - return parts[0] === '+' ? minutes : -minutes; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf(); - // Use low-level api, because this fn is low-level api. - res._d.setTime(res._d.valueOf() + diff); - utils_hooks__hooks.updateOffset(res, false); - return res; - } else { - return local__createLocal(input).local(); - } - } - - function getDateOffset (m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset() / 15) * 15; - } - - // HOOKS - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - utils_hooks__hooks.updateOffset = function () {}; - - // MOMENTS - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - function getSetOffset (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (!this.isValid()) { - return input != null ? this : NaN; - } - if (input != null) { - if (typeof input === 'string') { - input = offsetFromString(matchShortOffset, input); - } else if (Math.abs(input) < 16) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - utils_hooks__hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; - } else { - return this._isUTC ? offset : getDateOffset(this); - } - } - - function getSetZone (input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } - - this.utcOffset(input, keepLocalTime); - - return this; - } else { - return -this.utcOffset(); - } - } - - function setOffsetToUTC (keepLocalTime) { - return this.utcOffset(0, keepLocalTime); - } - - function setOffsetToLocal (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); - } - } - return this; - } - - function setOffsetToParsedOffset () { - if (this._tzm) { - this.utcOffset(this._tzm); - } else if (typeof this._i === 'string') { - this.utcOffset(offsetFromString(matchOffset, this._i)); - } - return this; - } - - function hasAlignedHourOffset (input) { - if (!this.isValid()) { - return false; - } - input = input ? local__createLocal(input).utcOffset() : 0; - - return (this.utcOffset() - input) % 60 === 0; - } - - function isDaylightSavingTime () { - return ( - this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset() - ); - } - - function isDaylightSavingTimeShifted () { - if (!isUndefined(this._isDSTShifted)) { - return this._isDSTShifted; - } - - var c = {}; - - copyConfig(c, this); - c = prepareConfig(c); - - if (c._a) { - var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); - this._isDSTShifted = this.isValid() && - compareArrays(c._a, other.toArray()) > 0; - } else { - this._isDSTShifted = false; - } - - return this._isDSTShifted; - } - - function isLocal () { - return this.isValid() ? !this._isUTC : false; - } - - function isUtcOffset () { - return this.isValid() ? this._isUTC : false; - } - - function isUtc () { - return this.isValid() ? this._isUTC && this._offset === 0 : false; - } - - // ASP.NET json date format regex - var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/; - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - // and further modified to allow for strings containing both week and day - var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; - - function create__createDuration (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - diffRes; - - if (isDuration(input)) { - duration = { - ms : input._milliseconds, - d : input._days, - M : input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : 0, - d : toInt(match[DATE]) * sign, - h : toInt(match[HOUR]) * sign, - m : toInt(match[MINUTE]) * sign, - s : toInt(match[SECOND]) * sign, - ms : toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : parseIso(match[2], sign), - M : parseIso(match[3], sign), - w : parseIso(match[4], sign), - d : parseIso(match[5], sign), - h : parseIso(match[6], sign), - m : parseIso(match[7], sign), - s : parseIso(match[8], sign) - }; - } else if (duration == null) {// checks for null or undefined - duration = {}; - } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } - - return ret; - } - - create__createDuration.fn = Duration.prototype; - - function parseIso (inp, sign) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - } - - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; - - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - if (!(base.isValid() && other.isValid())) { - return {milliseconds: 0, months: 0}; - } - - other = cloneWithOffset(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - function absRound (number) { - if (number < 0) { - return Math.round(-1 * number) * -1; - } else { - return Math.round(number); - } - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + - 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); - tmp = val; val = period; period = tmp; - } - - val = typeof val === 'string' ? +val : val; - dur = create__createDuration(val, period); - add_subtract__addSubtract(this, dur, direction); - return this; - }; - } - - function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = absRound(duration._days), - months = absRound(duration._months); - - if (!mom.isValid()) { - // No op - return; - } - - updateOffset = updateOffset == null ? true : updateOffset; - - if (milliseconds) { - mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); - } - if (days) { - get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); - } - if (months) { - setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - utils_hooks__hooks.updateOffset(mom, days || months); - } - } - - var add_subtract__add = createAdder(1, 'add'); - var add_subtract__subtract = createAdder(-1, 'subtract'); - - function getCalendarFormat(myMoment, now) { - var diff = myMoment.diff(now, 'days', true); - return diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - } - - function moment_calendar__calendar (time, formats) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're local/utc/offset or not. - var now = time || local__createLocal(), - sod = cloneWithOffset(now, this).startOf('day'), - format = utils_hooks__hooks.calendarFormat(this, sod) || 'sameElse'; - - var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); - - return this.format(output || this.localeData().calendar(format, this, local__createLocal(now))); - } - - function clone () { - return new Moment(this); - } - - function isAfter (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() > localInput.valueOf(); - } else { - return localInput.valueOf() < this.clone().startOf(units).valueOf(); - } - } - - function isBefore (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() < localInput.valueOf(); - } else { - return this.clone().endOf(units).valueOf() < localInput.valueOf(); - } - } - - function isBetween (from, to, units, inclusivity) { - inclusivity = inclusivity || '()'; - return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && - (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); - } - - function isSame (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input), - inputMs; - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units || 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() === localInput.valueOf(); - } else { - inputMs = localInput.valueOf(); - return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); - } - } - - function isSameOrAfter (input, units) { - return this.isSame(input, units) || this.isAfter(input,units); - } - - function isSameOrBefore (input, units) { - return this.isSame(input, units) || this.isBefore(input,units); - } - - function diff (input, units, asFloat) { - var that, - zoneDelta, - delta, output; - - if (!this.isValid()) { - return NaN; - } - - that = cloneWithOffset(input, this); - - if (!that.isValid()) { - return NaN; - } - - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; - - units = normalizeUnits(units); - - if (units === 'year' || units === 'month' || units === 'quarter') { - output = monthDiff(this, that); - if (units === 'quarter') { - output = output / 3; - } else if (units === 'year') { - output = output / 12; - } - } else { - delta = this - that; - output = units === 'second' ? delta / 1e3 : // 1000 - units === 'minute' ? delta / 6e4 : // 1000 * 60 - units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - delta; - } - return asFloat ? output : absFloor(output); - } - - function monthDiff (a, b) { - // difference in months - var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, adjust; - - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); - } - - //check for negative zero, return zero if negative zero - return -(wholeMonthDiff + adjust) || 0; - } - - utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; - utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; - - function toString () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - } - - function moment_format__toISOString () { - var m = this.clone().utc(); - if (0 < m.year() && m.year() <= 9999) { - if (isFunction(Date.prototype.toISOString)) { - // native implementation is ~50x faster, use it when we can - return this.toDate().toISOString(); - } else { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - } - - function format (inputString) { - if (!inputString) { - inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat; - } - var output = formatMoment(this, inputString); - return this.localeData().postformat(output); - } - - function from (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - local__createLocal(time).isValid())) { - return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function fromNow (withoutSuffix) { - return this.from(local__createLocal(), withoutSuffix); - } - - function to (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - local__createLocal(time).isValid())) { - return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function toNow (withoutSuffix) { - return this.to(local__createLocal(), withoutSuffix); - } - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - function locale (key) { - var newLocaleData; - - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = locale_locales__getLocale(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } - } - - var lang = deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ); - - function localeData () { - return this._locale; - } - - function startOf (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - case 'date': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } - if (units === 'isoWeek') { - this.isoWeekday(1); - } - - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } - - return this; - } - - function endOf (units) { - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond') { - return this; - } - - // 'date' is an alias for 'day', so it should be considered as such. - if (units === 'date') { - units = 'day'; - } - - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); - } - - function to_type__valueOf () { - return this._d.valueOf() - ((this._offset || 0) * 60000); - } - - function unix () { - return Math.floor(this.valueOf() / 1000); - } - - function toDate () { - return new Date(this.valueOf()); - } - - function toArray () { - var m = this; - return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; - } - - function toObject () { - var m = this; - return { - years: m.year(), - months: m.month(), - date: m.date(), - hours: m.hours(), - minutes: m.minutes(), - seconds: m.seconds(), - milliseconds: m.milliseconds() - }; - } - - function toJSON () { - // new Date(NaN).toJSON() === null - return this.isValid() ? this.toISOString() : null; - } - - function moment_valid__isValid () { - return valid__isValid(this); - } - - function parsingFlags () { - return extend({}, getParsingFlags(this)); - } - - function invalidAt () { - return getParsingFlags(this).overflow; - } - - function creationData() { - return { - input: this._i, - format: this._f, - locale: this._locale, - isUTC: this._isUTC, - strict: this._strict - }; - } - - // FORMATTING - - addFormatToken(0, ['gg', 2], 0, function () { - return this.weekYear() % 100; - }); - - addFormatToken(0, ['GG', 2], 0, function () { - return this.isoWeekYear() % 100; - }); - - function addWeekYearFormatToken (token, getter) { - addFormatToken(0, [token, token.length], 0, getter); - } - - addWeekYearFormatToken('gggg', 'weekYear'); - addWeekYearFormatToken('ggggg', 'weekYear'); - addWeekYearFormatToken('GGGG', 'isoWeekYear'); - addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - - // ALIASES - - addUnitAlias('weekYear', 'gg'); - addUnitAlias('isoWeekYear', 'GG'); - - // PRIORITY - - addUnitPriority('weekYear', 1); - addUnitPriority('isoWeekYear', 1); - - - // PARSING - - addRegexToken('G', matchSigned); - addRegexToken('g', matchSigned); - addRegexToken('GG', match1to2, match2); - addRegexToken('gg', match1to2, match2); - addRegexToken('GGGG', match1to4, match4); - addRegexToken('gggg', match1to4, match4); - addRegexToken('GGGGG', match1to6, match6); - addRegexToken('ggggg', match1to6, match6); - - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { - week[token.substr(0, 2)] = toInt(input); - }); - - addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { - week[token] = utils_hooks__hooks.parseTwoDigitYear(input); - }); - - // MOMENTS - - function getSetWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, - this.week(), - this.weekday(), - this.localeData()._week.dow, - this.localeData()._week.doy); - } - - function getSetISOWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, this.isoWeek(), this.isoWeekday(), 1, 4); - } - - function getISOWeeksInYear () { - return weeksInYear(this.year(), 1, 4); - } - - function getWeeksInYear () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - } - - function getSetWeekYearHelper(input, week, weekday, dow, doy) { - var weeksTarget; - if (input == null) { - return weekOfYear(this, dow, doy).year; - } else { - weeksTarget = weeksInYear(input, dow, doy); - if (week > weeksTarget) { - week = weeksTarget; - } - return setWeekAll.call(this, input, week, weekday, dow, doy); - } - } - - function setWeekAll(weekYear, week, weekday, dow, doy) { - var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), - date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); - - this.year(date.getUTCFullYear()); - this.month(date.getUTCMonth()); - this.date(date.getUTCDate()); - return this; - } - - // FORMATTING - - addFormatToken('Q', 0, 'Qo', 'quarter'); - - // ALIASES - - addUnitAlias('quarter', 'Q'); - - // PRIORITY - - addUnitPriority('quarter', 7); - - // PARSING - - addRegexToken('Q', match1); - addParseToken('Q', function (input, array) { - array[MONTH] = (toInt(input) - 1) * 3; - }); - - // MOMENTS - - function getSetQuarter (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - } - - // FORMATTING - - addFormatToken('D', ['DD', 2], 'Do', 'date'); - - // ALIASES - - addUnitAlias('date', 'D'); - - // PRIOROITY - addUnitPriority('date', 9); - - // PARSING - - addRegexToken('D', match1to2); - addRegexToken('DD', match1to2, match2); - addRegexToken('Do', function (isStrict, locale) { - return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; - }); - - addParseToken(['D', 'DD'], DATE); - addParseToken('Do', function (input, array) { - array[DATE] = toInt(input.match(match1to2)[0], 10); - }); - - // MOMENTS - - var getSetDayOfMonth = makeGetSet('Date', true); - - // FORMATTING - - addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - - // ALIASES - - addUnitAlias('dayOfYear', 'DDD'); - - // PRIORITY - addUnitPriority('dayOfYear', 4); - - // PARSING - - addRegexToken('DDD', match1to3); - addRegexToken('DDDD', match3); - addParseToken(['DDD', 'DDDD'], function (input, array, config) { - config._dayOfYear = toInt(input); - }); - - // HELPERS - - // MOMENTS - - function getSetDayOfYear (input) { - var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - } - - // FORMATTING - - addFormatToken('m', ['mm', 2], 0, 'minute'); - - // ALIASES - - addUnitAlias('minute', 'm'); - - // PRIORITY - - addUnitPriority('minute', 14); - - // PARSING - - addRegexToken('m', match1to2); - addRegexToken('mm', match1to2, match2); - addParseToken(['m', 'mm'], MINUTE); - - // MOMENTS - - var getSetMinute = makeGetSet('Minutes', false); - - // FORMATTING - - addFormatToken('s', ['ss', 2], 0, 'second'); - - // ALIASES - - addUnitAlias('second', 's'); - - // PRIORITY - - addUnitPriority('second', 15); - - // PARSING - - addRegexToken('s', match1to2); - addRegexToken('ss', match1to2, match2); - addParseToken(['s', 'ss'], SECOND); - - // MOMENTS - - var getSetSecond = makeGetSet('Seconds', false); - - // FORMATTING - - addFormatToken('S', 0, 0, function () { - return ~~(this.millisecond() / 100); - }); - - addFormatToken(0, ['SS', 2], 0, function () { - return ~~(this.millisecond() / 10); - }); - - addFormatToken(0, ['SSS', 3], 0, 'millisecond'); - addFormatToken(0, ['SSSS', 4], 0, function () { - return this.millisecond() * 10; - }); - addFormatToken(0, ['SSSSS', 5], 0, function () { - return this.millisecond() * 100; - }); - addFormatToken(0, ['SSSSSS', 6], 0, function () { - return this.millisecond() * 1000; - }); - addFormatToken(0, ['SSSSSSS', 7], 0, function () { - return this.millisecond() * 10000; - }); - addFormatToken(0, ['SSSSSSSS', 8], 0, function () { - return this.millisecond() * 100000; - }); - addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { - return this.millisecond() * 1000000; - }); - - - // ALIASES - - addUnitAlias('millisecond', 'ms'); - - // PRIORITY - - addUnitPriority('millisecond', 16); - - // PARSING - - addRegexToken('S', match1to3, match1); - addRegexToken('SS', match1to3, match2); - addRegexToken('SSS', match1to3, match3); - - var token; - for (token = 'SSSS'; token.length <= 9; token += 'S') { - addRegexToken(token, matchUnsigned); - } - - function parseMs(input, array) { - array[MILLISECOND] = toInt(('0.' + input) * 1000); - } - - for (token = 'S'; token.length <= 9; token += 'S') { - addParseToken(token, parseMs); - } - // MOMENTS - - var getSetMillisecond = makeGetSet('Milliseconds', false); - - // FORMATTING - - addFormatToken('z', 0, 0, 'zoneAbbr'); - addFormatToken('zz', 0, 0, 'zoneName'); - - // MOMENTS - - function getZoneAbbr () { - return this._isUTC ? 'UTC' : ''; - } - - function getZoneName () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - } - - var momentPrototype__proto = Moment.prototype; - - momentPrototype__proto.add = add_subtract__add; - momentPrototype__proto.calendar = moment_calendar__calendar; - momentPrototype__proto.clone = clone; - momentPrototype__proto.diff = diff; - momentPrototype__proto.endOf = endOf; - momentPrototype__proto.format = format; - momentPrototype__proto.from = from; - momentPrototype__proto.fromNow = fromNow; - momentPrototype__proto.to = to; - momentPrototype__proto.toNow = toNow; - momentPrototype__proto.get = stringGet; - momentPrototype__proto.invalidAt = invalidAt; - momentPrototype__proto.isAfter = isAfter; - momentPrototype__proto.isBefore = isBefore; - momentPrototype__proto.isBetween = isBetween; - momentPrototype__proto.isSame = isSame; - momentPrototype__proto.isSameOrAfter = isSameOrAfter; - momentPrototype__proto.isSameOrBefore = isSameOrBefore; - momentPrototype__proto.isValid = moment_valid__isValid; - momentPrototype__proto.lang = lang; - momentPrototype__proto.locale = locale; - momentPrototype__proto.localeData = localeData; - momentPrototype__proto.max = prototypeMax; - momentPrototype__proto.min = prototypeMin; - momentPrototype__proto.parsingFlags = parsingFlags; - momentPrototype__proto.set = stringSet; - momentPrototype__proto.startOf = startOf; - momentPrototype__proto.subtract = add_subtract__subtract; - momentPrototype__proto.toArray = toArray; - momentPrototype__proto.toObject = toObject; - momentPrototype__proto.toDate = toDate; - momentPrototype__proto.toISOString = moment_format__toISOString; - momentPrototype__proto.toJSON = toJSON; - momentPrototype__proto.toString = toString; - momentPrototype__proto.unix = unix; - momentPrototype__proto.valueOf = to_type__valueOf; - momentPrototype__proto.creationData = creationData; - - // Year - momentPrototype__proto.year = getSetYear; - momentPrototype__proto.isLeapYear = getIsLeapYear; - - // Week Year - momentPrototype__proto.weekYear = getSetWeekYear; - momentPrototype__proto.isoWeekYear = getSetISOWeekYear; - - // Quarter - momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; - - // Month - momentPrototype__proto.month = getSetMonth; - momentPrototype__proto.daysInMonth = getDaysInMonth; - - // Week - momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; - momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; - momentPrototype__proto.weeksInYear = getWeeksInYear; - momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; - - // Day - momentPrototype__proto.date = getSetDayOfMonth; - momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; - momentPrototype__proto.weekday = getSetLocaleDayOfWeek; - momentPrototype__proto.isoWeekday = getSetISODayOfWeek; - momentPrototype__proto.dayOfYear = getSetDayOfYear; - - // Hour - momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; - - // Minute - momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; - - // Second - momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; - - // Millisecond - momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; - - // Offset - momentPrototype__proto.utcOffset = getSetOffset; - momentPrototype__proto.utc = setOffsetToUTC; - momentPrototype__proto.local = setOffsetToLocal; - momentPrototype__proto.parseZone = setOffsetToParsedOffset; - momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; - momentPrototype__proto.isDST = isDaylightSavingTime; - momentPrototype__proto.isLocal = isLocal; - momentPrototype__proto.isUtcOffset = isUtcOffset; - momentPrototype__proto.isUtc = isUtc; - momentPrototype__proto.isUTC = isUtc; - - // Timezone - momentPrototype__proto.zoneAbbr = getZoneAbbr; - momentPrototype__proto.zoneName = getZoneName; - - // Deprecations - momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); - momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); - momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); - momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); - momentPrototype__proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); - - var momentPrototype = momentPrototype__proto; - - function moment__createUnix (input) { - return local__createLocal(input * 1000); - } - - function moment__createInZone () { - return local__createLocal.apply(null, arguments).parseZone(); - } - - function preParsePostFormat (string) { - return string; - } - - var prototype__proto = Locale.prototype; - - prototype__proto.calendar = locale_calendar__calendar; - prototype__proto.longDateFormat = longDateFormat; - prototype__proto.invalidDate = invalidDate; - prototype__proto.ordinal = ordinal; - prototype__proto.preparse = preParsePostFormat; - prototype__proto.postformat = preParsePostFormat; - prototype__proto.relativeTime = relative__relativeTime; - prototype__proto.pastFuture = pastFuture; - prototype__proto.set = locale_set__set; - - // Month - prototype__proto.months = localeMonths; - prototype__proto.monthsShort = localeMonthsShort; - prototype__proto.monthsParse = localeMonthsParse; - prototype__proto.monthsRegex = monthsRegex; - prototype__proto.monthsShortRegex = monthsShortRegex; - - // Week - prototype__proto.week = localeWeek; - prototype__proto.firstDayOfYear = localeFirstDayOfYear; - prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; - - // Day of Week - prototype__proto.weekdays = localeWeekdays; - prototype__proto.weekdaysMin = localeWeekdaysMin; - prototype__proto.weekdaysShort = localeWeekdaysShort; - prototype__proto.weekdaysParse = localeWeekdaysParse; - - prototype__proto.weekdaysRegex = weekdaysRegex; - prototype__proto.weekdaysShortRegex = weekdaysShortRegex; - prototype__proto.weekdaysMinRegex = weekdaysMinRegex; - - // Hours - prototype__proto.isPM = localeIsPM; - prototype__proto.meridiem = localeMeridiem; - - function lists__get (format, index, field, setter) { - var locale = locale_locales__getLocale(); - var utc = create_utc__createUTC().set(setter, index); - return locale[field](utc, format); - } - - function listMonthsImpl (format, index, field) { - if (typeof format === 'number') { - index = format; - format = undefined; - } - - format = format || ''; - - if (index != null) { - return lists__get(format, index, field, 'month'); - } - - var i; - var out = []; - for (i = 0; i < 12; i++) { - out[i] = lists__get(format, i, field, 'month'); - } - return out; - } - - // () - // (5) - // (fmt, 5) - // (fmt) - // (true) - // (true, 5) - // (true, fmt, 5) - // (true, fmt) - function listWeekdaysImpl (localeSorted, format, index, field) { - if (typeof localeSorted === 'boolean') { - if (typeof format === 'number') { - index = format; - format = undefined; - } - - format = format || ''; - } else { - format = localeSorted; - index = format; - localeSorted = false; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - format = format || ''; - } - - var locale = locale_locales__getLocale(), - shift = localeSorted ? locale._week.dow : 0; - - if (index != null) { - return lists__get(format, (index + shift) % 7, field, 'day'); - } - - var i; - var out = []; - for (i = 0; i < 7; i++) { - out[i] = lists__get(format, (i + shift) % 7, field, 'day'); - } - return out; - } - - function lists__listMonths (format, index) { - return listMonthsImpl(format, index, 'months'); - } - - function lists__listMonthsShort (format, index) { - return listMonthsImpl(format, index, 'monthsShort'); - } - - function lists__listWeekdays (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); - } - - function lists__listWeekdaysShort (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); - } - - function lists__listWeekdaysMin (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); - } - - locale_locales__getSetGlobalLocale('en', { - ordinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - // Side effect imports - utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); - utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); - - var mathAbs = Math.abs; - - function duration_abs__abs () { - var data = this._data; - - this._milliseconds = mathAbs(this._milliseconds); - this._days = mathAbs(this._days); - this._months = mathAbs(this._months); - - data.milliseconds = mathAbs(data.milliseconds); - data.seconds = mathAbs(data.seconds); - data.minutes = mathAbs(data.minutes); - data.hours = mathAbs(data.hours); - data.months = mathAbs(data.months); - data.years = mathAbs(data.years); - - return this; - } - - function duration_add_subtract__addSubtract (duration, input, value, direction) { - var other = create__createDuration(input, value); - - duration._milliseconds += direction * other._milliseconds; - duration._days += direction * other._days; - duration._months += direction * other._months; - - return duration._bubble(); - } - - // supports only 2.0-style add(1, 's') or add(duration) - function duration_add_subtract__add (input, value) { - return duration_add_subtract__addSubtract(this, input, value, 1); - } - - // supports only 2.0-style subtract(1, 's') or subtract(duration) - function duration_add_subtract__subtract (input, value) { - return duration_add_subtract__addSubtract(this, input, value, -1); - } - - function absCeil (number) { - if (number < 0) { - return Math.floor(number); - } else { - return Math.ceil(number); - } - } - - function bubble () { - var milliseconds = this._milliseconds; - var days = this._days; - var months = this._months; - var data = this._data; - var seconds, minutes, hours, years, monthsFromDays; - - // if we have a mix of positive and negative values, bubble down first - // check: https://github.com/moment/moment/issues/2166 - if (!((milliseconds >= 0 && days >= 0 && months >= 0) || - (milliseconds <= 0 && days <= 0 && months <= 0))) { - milliseconds += absCeil(monthsToDays(months) + days) * 864e5; - days = 0; - months = 0; - } - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absFloor(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absFloor(seconds / 60); - data.minutes = minutes % 60; - - hours = absFloor(minutes / 60); - data.hours = hours % 24; - - days += absFloor(hours / 24); - - // convert days to months - monthsFromDays = absFloor(daysToMonths(days)); - months += monthsFromDays; - days -= absCeil(monthsToDays(monthsFromDays)); - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - - return this; - } - - function daysToMonths (days) { - // 400 years have 146097 days (taking into account leap year rules) - // 400 years have 12 months === 4800 - return days * 4800 / 146097; - } - - function monthsToDays (months) { - // the reverse of daysToMonths - return months * 146097 / 4800; - } - - function as (units) { - var days; - var months; - var milliseconds = this._milliseconds; - - units = normalizeUnits(units); - - if (units === 'month' || units === 'year') { - days = this._days + milliseconds / 864e5; - months = this._months + daysToMonths(days); - return units === 'month' ? months : months / 12; - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(monthsToDays(this._months)); - switch (units) { - case 'week' : return days / 7 + milliseconds / 6048e5; - case 'day' : return days + milliseconds / 864e5; - case 'hour' : return days * 24 + milliseconds / 36e5; - case 'minute' : return days * 1440 + milliseconds / 6e4; - case 'second' : return days * 86400 + milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 864e5) + milliseconds; - default: throw new Error('Unknown unit ' + units); - } - } - } - - // TODO: Use this.as('ms')? - function duration_as__valueOf () { - return ( - this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6 - ); - } - - function makeAs (alias) { - return function () { - return this.as(alias); - }; - } - - var asMilliseconds = makeAs('ms'); - var asSeconds = makeAs('s'); - var asMinutes = makeAs('m'); - var asHours = makeAs('h'); - var asDays = makeAs('d'); - var asWeeks = makeAs('w'); - var asMonths = makeAs('M'); - var asYears = makeAs('y'); - - function duration_get__get (units) { - units = normalizeUnits(units); - return this[units + 's'](); - } - - function makeGetter(name) { - return function () { - return this._data[name]; - }; - } - - var milliseconds = makeGetter('milliseconds'); - var seconds = makeGetter('seconds'); - var minutes = makeGetter('minutes'); - var hours = makeGetter('hours'); - var days = makeGetter('days'); - var months = makeGetter('months'); - var years = makeGetter('years'); - - function weeks () { - return absFloor(this.days() / 7); - } - - var round = Math.round; - var thresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }; - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { - var duration = create__createDuration(posNegDuration).abs(); - var seconds = round(duration.as('s')); - var minutes = round(duration.as('m')); - var hours = round(duration.as('h')); - var days = round(duration.as('d')); - var months = round(duration.as('M')); - var years = round(duration.as('y')); - - var a = seconds < thresholds.s && ['s', seconds] || - minutes <= 1 && ['m'] || - minutes < thresholds.m && ['mm', minutes] || - hours <= 1 && ['h'] || - hours < thresholds.h && ['hh', hours] || - days <= 1 && ['d'] || - days < thresholds.d && ['dd', days] || - months <= 1 && ['M'] || - months < thresholds.M && ['MM', months] || - years <= 1 && ['y'] || ['yy', years]; - - a[2] = withoutSuffix; - a[3] = +posNegDuration > 0; - a[4] = locale; - return substituteTimeAgo.apply(null, a); - } - - // This function allows you to set the rounding function for relative time strings - function duration_humanize__getSetRelativeTimeRounding (roundingFunction) { - if (roundingFunction === undefined) { - return round; - } - if (typeof(roundingFunction) === 'function') { - round = roundingFunction; - return true; - } - return false; - } - - // This function allows you to set a threshold for relative time strings - function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { - if (thresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return thresholds[threshold]; - } - thresholds[threshold] = limit; - return true; - } - - function humanize (withSuffix) { - var locale = this.localeData(); - var output = duration_humanize__relativeTime(this, !withSuffix, locale); - - if (withSuffix) { - output = locale.pastFuture(+this, output); - } - - return locale.postformat(output); - } - - var iso_string__abs = Math.abs; - - function iso_string__toISOString() { - // for ISO strings we do not use the normal bubbling rules: - // * milliseconds bubble up until they become hours - // * days do not bubble at all - // * months bubble up until they become years - // This is because there is no context-free conversion between hours and days - // (think of clock changes) - // and also not between days and months (28-31 days per month) - var seconds = iso_string__abs(this._milliseconds) / 1000; - var days = iso_string__abs(this._days); - var months = iso_string__abs(this._months); - var minutes, hours, years; - - // 3600 seconds -> 60 minutes -> 1 hour - minutes = absFloor(seconds / 60); - hours = absFloor(minutes / 60); - seconds %= 60; - minutes %= 60; - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var Y = years; - var M = months; - var D = days; - var h = hours; - var m = minutes; - var s = seconds; - var total = this.asSeconds(); - - if (!total) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (total < 0 ? '-' : '') + - 'P' + - (Y ? Y + 'Y' : '') + - (M ? M + 'M' : '') + - (D ? D + 'D' : '') + - ((h || m || s) ? 'T' : '') + - (h ? h + 'H' : '') + - (m ? m + 'M' : '') + - (s ? s + 'S' : ''); - } - - var duration_prototype__proto = Duration.prototype; - - duration_prototype__proto.abs = duration_abs__abs; - duration_prototype__proto.add = duration_add_subtract__add; - duration_prototype__proto.subtract = duration_add_subtract__subtract; - duration_prototype__proto.as = as; - duration_prototype__proto.asMilliseconds = asMilliseconds; - duration_prototype__proto.asSeconds = asSeconds; - duration_prototype__proto.asMinutes = asMinutes; - duration_prototype__proto.asHours = asHours; - duration_prototype__proto.asDays = asDays; - duration_prototype__proto.asWeeks = asWeeks; - duration_prototype__proto.asMonths = asMonths; - duration_prototype__proto.asYears = asYears; - duration_prototype__proto.valueOf = duration_as__valueOf; - duration_prototype__proto._bubble = bubble; - duration_prototype__proto.get = duration_get__get; - duration_prototype__proto.milliseconds = milliseconds; - duration_prototype__proto.seconds = seconds; - duration_prototype__proto.minutes = minutes; - duration_prototype__proto.hours = hours; - duration_prototype__proto.days = days; - duration_prototype__proto.weeks = weeks; - duration_prototype__proto.months = months; - duration_prototype__proto.years = years; - duration_prototype__proto.humanize = humanize; - duration_prototype__proto.toISOString = iso_string__toISOString; - duration_prototype__proto.toString = iso_string__toISOString; - duration_prototype__proto.toJSON = iso_string__toISOString; - duration_prototype__proto.locale = locale; - duration_prototype__proto.localeData = localeData; - - // Deprecations - duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); - duration_prototype__proto.lang = lang; - - // Side effect imports - - // FORMATTING - - addFormatToken('X', 0, 0, 'unix'); - addFormatToken('x', 0, 0, 'valueOf'); - - // PARSING - - addRegexToken('x', matchSigned); - addRegexToken('X', matchTimestamp); - addParseToken('X', function (input, array, config) { - config._d = new Date(parseFloat(input, 10) * 1000); - }); - addParseToken('x', function (input, array, config) { - config._d = new Date(toInt(input)); - }); - - // Side effect imports - - - utils_hooks__hooks.version = '2.14.1'; - - setHookCallback(local__createLocal); - - utils_hooks__hooks.fn = momentPrototype; - utils_hooks__hooks.min = min; - utils_hooks__hooks.max = max; - utils_hooks__hooks.now = now; - utils_hooks__hooks.utc = create_utc__createUTC; - utils_hooks__hooks.unix = moment__createUnix; - utils_hooks__hooks.months = lists__listMonths; - utils_hooks__hooks.isDate = isDate; - utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; - utils_hooks__hooks.invalid = valid__createInvalid; - utils_hooks__hooks.duration = create__createDuration; - utils_hooks__hooks.isMoment = isMoment; - utils_hooks__hooks.weekdays = lists__listWeekdays; - utils_hooks__hooks.parseZone = moment__createInZone; - utils_hooks__hooks.localeData = locale_locales__getLocale; - utils_hooks__hooks.isDuration = isDuration; - utils_hooks__hooks.monthsShort = lists__listMonthsShort; - utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; - utils_hooks__hooks.defineLocale = defineLocale; - utils_hooks__hooks.updateLocale = updateLocale; - utils_hooks__hooks.locales = locale_locales__listLocales; - utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; - utils_hooks__hooks.normalizeUnits = normalizeUnits; - utils_hooks__hooks.relativeTimeRounding = duration_humanize__getSetRelativeTimeRounding; - utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; - utils_hooks__hooks.calendarFormat = getCalendarFormat; - utils_hooks__hooks.prototype = momentPrototype; - - var _moment = utils_hooks__hooks; - - return _moment; - -})); \ No newline at end of file From d12dfe2f562c040cdbbd7ea930a970549b79f8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 3 Sep 2016 02:54:02 +0200 Subject: [PATCH 151/192] =?UTF-8?q?Passage=20de=20sha1=20=C3=A0=20sha256?= =?UTF-8?q?=20pour=20le=20pwd=20sur=20Account?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/backends.py | 4 ++-- kfet/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kfet/backends.py b/kfet/backends.py index 6f917310..62b2d820 100644 --- a/kfet/backends.py +++ b/kfet/backends.py @@ -18,8 +18,8 @@ class KFetBackend(object): return None try: - password_sha1 = hashlib.sha1(password.encode()).hexdigest() - account = Account.objects.get(password=password_sha1) + password_sha256 = hashlib.sha256(password.encode()).hexdigest() + account = Account.objects.get(password=password_sha256) user = account.cofprofile.user except Account.DoesNotExist: return None diff --git a/kfet/views.py b/kfet/views.py index 0d6e2901..f2f98f8f 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -319,9 +319,9 @@ def account_update(request, trigramme): if (request.user.has_perm('kfet.change_account_password') and pwd_form.is_valid()): pwd = pwd_form.cleaned_data['pwd1'] - pwd_sha1 = hashlib.sha1(pwd.encode()).hexdigest() + pwd_sha256 = hashlib.sha256(pwd.encode()).hexdigest() Account.objects.filter(pk=account.pk).update( - password = pwd_sha1) + password = pwd_sha256) messages.success(request, 'Mot de passe mis à jour') # Checking perm to manage perms From a4322301289a218048b434ea85a2dfe17cdfa3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 3 Sep 2016 13:50:40 +0200 Subject: [PATCH 152/192] Fixs annulations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix oublie de @staticmethod pour Settings.CANCEl_DURATION() - Mise en cache de Settings.CANCEL_DURATION - Fix sur cancel_operations : mauvais modèles et at --- kfet/models.py | 11 +++++++++-- kfet/views.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index 69098ac9..07b56f2e 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -633,11 +633,17 @@ class Settings(models.Model): 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: - return Settings.setting_inst("CANCEL_DURATION").value_duration + cancel_duration = Settings.setting_inst("CANCEL_DURATION").value_duration except Settings.DoesNotExist: - return timedelta() + cancel_duration = timedelta() + cache.set('CANCEL_DURATION', cancel_duration) + return cancel_duration @staticmethod def create_missing(): @@ -667,6 +673,7 @@ class Settings(models.Model): def empty_cache(): cache.delete_many([ 'SUBVENTION_COF','OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT', + 'CANCEL_DURATION', ]) class GenericTeamToken(models.Model): diff --git a/kfet/views.py b/kfet/views.py index f2f98f8f..f7f8728e 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1075,10 +1075,10 @@ def kpsul_cancel_operations(request): # Note : si InventoryArticle est maj par .save(), stock_error # est recalculé automatiquement if ope.article and ope.article_nb: - last_stock = (ArticleInventory.objects + last_stock = (InventoryArticle.objects .select_related('inventory') .filter(article=ope.article) - .order_by('at') + .order_by('inventory__at') .last()) if not last_stock or last_stock.inventory.at < ope.group.at: to_articles_stocks[ope.article] += ope.article_nb From 0e90949f917d92fd101e4aaea15f8f848b780cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 3 Sep 2016 14:06:51 +0200 Subject: [PATCH 153/192] =?UTF-8?q?Ajout=20"K-F=C3=AAt"=20dans=20le=20nom?= =?UTF-8?q?=20d'un=20groupe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ajout automatique de "K-Fêt" dans le nom d'un groupe empêche de créer un groupe sans "K-Fêt" et donc de ne pas le voir dans la liste des groupes. Une indication est ajouté dans le formulaire pour créer/modifier un groupe pour indiquer que "K-Fêt" va être ajouté au début du nom du groupe. --- kfet/forms.py | 5 +++++ kfet/templates/kfet/account_group_form.html | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/kfet/forms.py b/kfet/forms.py index 364388b0..d2b0cfef 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -130,6 +130,11 @@ 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'] diff --git a/kfet/templates/kfet/account_group_form.html b/kfet/templates/kfet/account_group_form.html index 2dd12fbf..e37bdd89 100644 --- a/kfet/templates/kfet/account_group_form.html +++ b/kfet/templates/kfet/account_group_form.html @@ -10,7 +10,19 @@
      {% csrf_token %} - {{ form.as_p }} +
      + {{ form.name.errors }} + {{ form.name.label_tag }} +
      + K-Fêt + {{ form.name }} +
      +
      +
      + {{ form.permissions.errors }} + {{ form.permissions.label_tag }} + {{ form.permissions }} +
      @@ -18,6 +30,7 @@ $(document).ready(function() { $("select").multipleSelect({ width: 500, + filter: true, }); }); From 88aad45fbb177470b1c5ab6bfc4da69d3f070987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 3 Sep 2016 15:21:26 +0200 Subject: [PATCH 154/192] =?UTF-8?q?Am=C3=A9lioration=20d=C3=A9but=20du=20f?= =?UTF-8?q?orm=20account=5Fcreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/static/kfet/css/index.css | 69 +++++++++++++++++++ kfet/templates/kfet/account_create.html | 47 ++++++++----- .../kfet/account_create_autocomplete.html | 14 ++-- kfet/templates/kfet/kpsul.html | 6 ++ kfet/views.py | 3 +- 5 files changed, 115 insertions(+), 24 deletions(-) diff --git a/kfet/static/kfet/css/index.css b/kfet/static/kfet/css/index.css index 886aa8ae..c7f44821 100644 --- a/kfet/static/kfet/css/index.css +++ b/kfet/static/kfet/css/index.css @@ -189,3 +189,72 @@ textarea { padding-left: 20px; font-size:25px; } + +/* + * Pages formulaires seuls + */ + +.form-only .content-form { + margin:15px; + + background:#fff; + + padding:15px; +} + +.form-only #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_results { + top:0 !important; + left:0 !important; +} + +#search_results ul { + list-style-type:none; + padding:0; + background:rgba(255,255,255,0.8); +} + +#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; +} diff --git a/kfet/templates/kfet/account_create.html b/kfet/templates/kfet/account_create.html index ef6bc4ff..f875ae4a 100644 --- a/kfet/templates/kfet/account_create.html +++ b/kfet/templates/kfet/account_create.html @@ -13,19 +13,31 @@ {% include 'kfet/base_messages.html' %} -
      - {% csrf_token %} - {{ trigramme_form }} -
      - -
      -
      - {% include 'kfet/account_create_form.html' %} +
      +
      +
      + + {% csrf_token %} +
      + {{ trigramme_form.trigramme.errors }} + {{ trigramme_form.trigramme }} +
      +
      + +
      +
      +
      +
      + {% include 'kfet/account_create_form.html' %} +
      + {% if not perms.kfet.add_account %} + Authentification: + + {% endif %} +
      - {% if not perms.kfet.add_account %} - - {% endif %} - +
      +
      +{% endblock %} + +{% block content-header-title %}Création d'un compte{% endblock %} + +{% block content %} + +{% include 'kfet/base_messages.html' %} + +
      +
      +
      + + {% csrf_token %} +
      + {{ trigramme_form.trigramme.errors }} + {{ trigramme_form.trigramme }} + {{ balance_form }} +
      +
      + +
      +
      +
      +
      +
      + {% include 'kfet/account_create_form.html' %} +
      + {% if not perms.kfet.add_account %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} +
      + +
      +
      +
      + + +{% endblock %} diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html index 881a5fa9..faab76dd 100644 --- a/kfet/templates/kfet/history.html +++ b/kfet/templates/kfet/history.html @@ -212,9 +212,11 @@ $(document).ready(function() { content += '
    • '+data['errors']['missing_perms'][i]+'
    • '; content += ''; } - if ('negative' in data['errors']) + if ('negative' in data['errors']) { for (var i=0; iAutorisation de négatif requise pour '+data['errors']['negative'][i]+''; + } + } return content; } diff --git a/kfet/urls.py b/kfet/urls.py index 3422c671..e0bacf9a 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -31,6 +31,8 @@ urlpatterns = [ # Account - Create url(r'^accounts/new$', views.account_create, name = 'kfet.account.create'), + url(r'^accounts/new_special$', views.account_create_special, + name = 'kfet.account.create_special'), url(r'^accounts/new/user/(?P.+)$', views.account_create_ajax, name = 'kfet.account.create.fromuser'), url(r'^accounts/new/clipper/(?P.+)$', views.account_create_ajax, diff --git a/kfet/views.py b/kfet/views.py index f9c4d288..53c3412c 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -83,6 +83,81 @@ def account_is_validandfree_ajax(request): # Account - Create +@login_required +@teamkfet_required +def account_create_special(request): + + # Enregistrement + if request.method == "POST": + trigramme_form = AccountTriForm(request.POST, initial={'balance':0}) + balance_form = AccountBalanceForm(request.POST) + + # Peuplement des forms + username = request.POST.get('username') + login_clipper = request.POST.get('login_clipper') + + forms = get_account_create_forms( + request, username=username, login_clipper=login_clipper) + + account_form = forms['account_form'] + cof_form = forms['cof_form'] + user_form = forms['user_form'] + + if all((user_form.is_valid(), cof_form.is_valid(), + trigramme_form.is_valid(), account_form.is_valid(), + balance_form.is_valid())): + # Checking permission + if not request.user.has_perm('kfet.special_add_account'): + messages.error(request, 'Permission refusée') + else: + data = {} + # Fill data for Account.save() + put_cleaned_data_in_dict(data, user_form) + put_cleaned_data_in_dict(data, cof_form) + + try: + account = trigramme_form.save(data = data) + account_form = AccountNoTriForm(request.POST, instance=account) + account_form.save() + balance_form = AccountBalanceForm(request.POST, instance=account) + balance_form.save() + amount = balance_form.cleaned_data['balance'] + checkout = Checkout.objects.get(name='Initial') + is_cof = account.is_cof + opegroup = OperationGroup.objects.create( + on_acc=account, + checkout=checkout, + amount = amount, + is_cof = account.is_cof) + ope = Operation.objects.create( + group = opegroup, + type = Operation.INITIAL, + amount = amount, + is_checkout = False) + messages.success(request, 'Compte créé : %s' % account.trigramme) + return redirect('kfet.account.create') + except Account.UserHasAccount as e: + messages.error(request, \ + "Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme) + else: + initial = { 'trigramme': request.GET.get('trigramme', '') } + trigramme_form = AccountTriForm(initial = initial) + balance_form = AccountBalanceForm(initial = {'balance': 0}) + account_form = None + cof_form = None + user_form = None + + return render(request, "kfet/account_create_special.html", { + 'trigramme_form': trigramme_form, + 'account_form': account_form, + 'cof_form': cof_form, + 'user_form': user_form, + 'balance_form': balance_form, + }) + + +# Account - Create + @login_required @teamkfet_required def account_create(request): From 4ff963b5cb1b7e711dc696fd301a8be49803d5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 08:19:28 +0200 Subject: [PATCH 179/192] Fix account_update --- kfet/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/views.py b/kfet/views.py index 53c3412c..1409d61f 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -228,7 +228,7 @@ def get_account_create_forms(request=None, username=None, login_clipper=None): user = User.objects.get(username=login_clipper) # Ici, on nous a menti, le user existe déjà username = user.username - login_clipper = None + clipper = None except User.DoesNotExist: # Clipper (sans user déjà existant) From 3745485e6cad286081eb7339d3f328eb9ef48cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 13:11:02 +0200 Subject: [PATCH 180/192] =?UTF-8?q?Fix=20urls=20et=20type=20op=C3=A9ration?= =?UTF-8?q?=20'initial'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/static/kfet/js/history.js | 3 +++ kfet/templates/kfet/base_nav.html | 2 +- kfet/templates/kfet/kpsul.html | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index cc4748ab..291c106d 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -31,6 +31,9 @@ function KHistory(options={}) { if (ope['type'] == 'purchase') { infos1 = ope['article_nb']; infos2 = ope['article__name']; + } else if (ope['type'] == 'initial') { + infos1 = parsed_amount.toFixed(2)+'€'; + infos2 = 'Initial'; } else { infos1 = parsed_amount.toFixed(2)+'€'; infos2 = (ope['type'] == 'deposit') ? 'Charge' : 'Retrait'; diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index 4bfc7be4..b5c98375 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -51,7 +51,7 @@ {% endif %} {% if user.is_authenticated %} -
    • +
    • {% endif %}
      diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 56ba42d9..3ac32345 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -192,7 +192,9 @@ $(document).ready(function() { var buttons = ''; if (account_data['id'] != 0) { - buttons += ''; + var url_base = '{% url 'kfet.account.read' 'LIQ' %}'; + url_base = url_base.substr(0, url_base.length - 3); + buttons += ''; } if (account_data['id'] == 0) { var trigramme = triInput.val().toUpperCase(); From 1125b553d0167f1277bbee891021d4dad0e589be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 13:31:09 +0200 Subject: [PATCH 181/192] Fix perms perform_transfers --- kfet/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/views.py b/kfet/views.py index 1409d61f..d0bc54fb 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1391,7 +1391,7 @@ def perform_transfers(request): transfers = transfer_formset.save(commit = False) # Initializing vars - required_perms = set() # Required perms to perform all transfers + required_perms = set('kfet.add_transfer') # Required perms to perform all transfers to_accounts_balances = defaultdict(lambda:0) # For balances of accounts for transfer in transfers: From 16fe7eb99494aea03de7dc17deaabec489bb02e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 13:59:14 +0200 Subject: [PATCH 182/192] =?UTF-8?q?Ajout=20champ=20cr=C3=A9ation=20sur=20m?= =?UTF-8?q?od=C3=A8le=20Account?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/migrations/0046_account_created_at.py | 19 +++++++++++++++++++ kfet/models.py | 1 + 2 files changed, 20 insertions(+) create mode 100644 kfet/migrations/0046_account_created_at.py diff --git a/kfet/migrations/0046_account_created_at.py b/kfet/migrations/0046_account_created_at.py new file mode 100644 index 00000000..a624c0fb --- /dev/null +++ b/kfet/migrations/0046_account_created_at.py @@ -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), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 6a6807aa..f1eb2fca 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -40,6 +40,7 @@ class Account(models.Model): 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( From 6c54d582ede400bb7d4961cadfe0ac69e23a3915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 14:39:31 +0200 Subject: [PATCH 183/192] =?UTF-8?q?Limite=20de=20l'historique=20charg?= =?UTF-8?q?=C3=A9=20sur=20K-Psul?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/kpsul.html | 1 + kfet/views.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 3ac32345..b5f768eb 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -959,6 +959,7 @@ $(document).ready(function() { function getHistory() { var data = { from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), + limit: 100, }; $.ajax({ dataType: "json", diff --git a/kfet/views.py b/kfet/views.py index d0bc54fb..05eb1d16 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1248,6 +1248,7 @@ def history_json(request): # Récupération des paramètres from_date = request.POST.get('from', None) to_date = request.POST.get('to', None) + limit = request.POST.get('limit', None); checkouts = request.POST.getlist('checkouts[]', None) accounts = request.POST.getlist('accounts[]', None) @@ -1274,6 +1275,9 @@ def history_json(request): # Un non-membre de l'équipe n'a que accès à son historique if not request.user.has_perm('kfet.is_team'): opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet) + if limit: + opegroups = opegroups[:limit] + # Construction de la réponse opegroups_list = [] From fa83afc5f305183d806914b11e49873cb826220d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 14:46:45 +0200 Subject: [PATCH 184/192] =?UTF-8?q?Limite=20de=20l'historique=20charg?= =?UTF-8?q?=C3=A9=20sur=20K-Psul?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kfet/templates/kfet/kpsul.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index b5f768eb..6d484b20 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -959,7 +959,7 @@ $(document).ready(function() { function getHistory() { var data = { from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), - limit: 100, + limit: 20, }; $.ajax({ dataType: "json", From 912d970029647d195a2041902dc78cafcd927498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 15:57:08 +0200 Subject: [PATCH 185/192] Fix JS K-Psul --- kfet/templates/kfet/kpsul.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 6d484b20..f44f5c80 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -533,7 +533,7 @@ $(document).ready(function() { return false; } }); - if (!added) articles_container.find('[data-category='+article['category_id']+']').after(article_html); + if (!added) articles_container.find('.category[data-category='+article['category_id']+']').after(article_html); // Pour l'autocomplétion articlesList.push([article['name'],article['id'],article['category_id'],article['price']]); } From cd64f2027584575193987dfe6cf6c4e51d0a6796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 18:09:34 +0200 Subject: [PATCH 186/192] fix checkout data --- kfet/templates/kfet/kpsul.html | 1 - kfet/views.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index f44f5c80..f8b2c791 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -959,7 +959,6 @@ $(document).ready(function() { function getHistory() { var data = { from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), - limit: 20, }; $.ajax({ dataType: "json", diff --git a/kfet/views.py b/kfet/views.py index 05eb1d16..a659278f 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -819,7 +819,9 @@ def account_read_json(request): @teamkfet_required def kpsul_checkout_data(request): - pk = request.POST.get('pk', 0) + pk = request.POST.get('pk', 0) + if not pk: + pk = 0 try: data = (Checkout.objects .annotate( From db4ae73dfd972281accb73e34878cd7b4e864d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 19:19:09 +0200 Subject: [PATCH 187/192] Fix acc neg --- kfet/models.py | 2 +- kfet/views.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index f1eb2fca..419cd0a0 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -84,7 +84,7 @@ class Account(models.Model): @property def real_balance(self): if (hasattr(self, 'negative')): - return self.balance + self.negative.balance_offset + return self.balance - self.negative.balance_offset return self.balance @property diff --git a/kfet/views.py b/kfet/views.py index a659278f..736dbe8a 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -354,6 +354,9 @@ def account_update(request, trigramme): account_form = AccountForm(instance=account) cof_form = CofRestrictForm(instance=account.cofprofile) pwd_form = AccountPwdForm() + if account.balance < 0 and not hasattr(account, 'negative'): + AccountNegative.objects.create(account=account, start=timezone.now()) + account.refresh_from_db() if hasattr(account, 'negative'): negative_form = AccountNegativeForm(instance=account.negative) else: @@ -417,7 +420,7 @@ def account_update(request, trigramme): balance_offset_new = negative_form.cleaned_data['balance_offset'] if not balance_offset_new: balance_offset_new = 0 - balance_offset_diff = balance_offset_old - balance_offset_new + balance_offset_diff = balance_offset_new - balance_offset_old Account.objects.filter(pk=account.pk).update( balance = F('balance') + balance_offset_diff) negative_form.save() @@ -430,7 +433,9 @@ def account_update(request, trigramme): if request.user == account.user: missing_perm = False + account.refresh_from_db() user_form = UserRestrictForm(request.POST, instance=account.user) + account_form = AccountRestrictForm(request.POST, instance=account) if user_form.is_valid() and account_form.is_valid(): user_form.save() From 3d2583e2a0052f10143d6650b6f4f6862231d315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 5 Sep 2016 19:50:16 +0200 Subject: [PATCH 188/192] Fix url pour production --- kfet/static/kfet/js/kfet.js | 4 +++- kfet/templates/kfet/history.html | 4 +++- kfet/templates/kfet/kpsul.html | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 0e2bad18..7aa1d963 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -66,8 +66,10 @@ function getErrorsHtml(data) { content += ''; } 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; iAutorisation de négatif requise pour '+data['errors']['negative'][i]+''; + content += 'Autorisation de négatif requise pour '+data['errors']['negative'][i]+''; } } if ('addcost' in data['errors']) { diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html index faab76dd..091b4f2f 100644 --- a/kfet/templates/kfet/history.html +++ b/kfet/templates/kfet/history.html @@ -213,8 +213,10 @@ $(document).ready(function() { content += ''; } 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; iAutorisation de négatif requise pour '+data['errors']['negative'][i]+''; + content += 'Autorisation de négatif requise pour '+data['errors']['negative'][i]+''; } } return content; diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index f8b2c791..1694d57f 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -302,8 +302,10 @@ $(document).ready(function() { $('#checkout-last_statement_at').text(at_formated); var buttons = ''; if (checkout_data['id'] !== 0) { - buttons += ''; - buttons += ''; + var url_base = "{% url 'kfet.checkoutstatement.create' 1 %}"; + url_base = url_base.substr(0,url_base.length - 16); + buttons += ''; + buttons += ''; } checkout_container.find('.buttons').html(buttons); } From 9efe209689f5b08c74ad164c050d353af54152ff Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Mon, 5 Sep 2016 20:08:43 +0200 Subject: [PATCH 189/192] Modifications graphiques --- bda/templates/descriptions.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bda/templates/descriptions.html b/bda/templates/descriptions.html index 44fe16ae..404bf89c 100644 --- a/bda/templates/descriptions.html +++ b/bda/templates/descriptions.html @@ -20,6 +20,7 @@ .descTable{ width: auto; margin: 0 auto 1em; + border-bottom: 2px solid; border-collapse: collapse; border-spacing: 0; font-size: 14px; @@ -46,10 +47,7 @@
      - - - - + {% if show.image %} - + {% endif %} From 4237b842b401b5adc940d3a61c4a225e3f9b23b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Mon, 5 Sep 2016 23:47:16 +0200 Subject: [PATCH 190/192] Compat IE et mobiles --- gestioncof/templates/base.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gestioncof/templates/base.html b/gestioncof/templates/base.html index 185ebd44..2a718951 100644 --- a/gestioncof/templates/base.html +++ b/gestioncof/templates/base.html @@ -4,10 +4,12 @@ {{ site.name }} - - + + + + {% block extra_head %}{% endblock %} From 7d02f29e488c4576369ab6fa83b29622a4ced623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 6 Sep 2016 01:26:44 +0200 Subject: [PATCH 191/192] =?UTF-8?q?Pr=C3=A9paration=20pour=20daphne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apache/__init__.py | 0 apache/asgi.py | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 apache/__init__.py create mode 100644 apache/asgi.py diff --git a/apache/__init__.py b/apache/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apache/asgi.py b/apache/asgi.py new file mode 100644 index 00000000..eb18c7b2 --- /dev/null +++ b/apache/asgi.py @@ -0,0 +1,6 @@ +import os +from channels.asgi import get_channel_layer + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings") + +channel_layer = get_channel_layer() From 5b2c3e3caece39c1fb53612664f434ef72412a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Thu, 8 Sep 2016 12:52:07 +0200 Subject: [PATCH 192/192] Fix tabs --- bda/templates/descriptions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bda/templates/descriptions.html b/bda/templates/descriptions.html index 404bf89c..7eb8b93b 100644 --- a/bda/templates/descriptions.html +++ b/bda/templates/descriptions.html @@ -20,7 +20,7 @@ .descTable{ width: auto; margin: 0 auto 1em; - border-bottom: 2px solid; + border-bottom: 2px solid; border-collapse: collapse; border-spacing: 0; font-size: 14px;
      ").addClass("cw").text("#"));c.isBefore(f.clone().endOf("w"));)b.append(a("").addClass("dow").text(c.format("dd"))),c.add(1,"d");o.find(".datepicker-days thead").append(b)},M=function(a){return d.disabledDates[a.format("YYYY-MM-DD")]===!0},N=function(a){return d.enabledDates[a.format("YYYY-MM-DD")]===!0},O=function(a){return d.disabledHours[a.format("H")]===!0},P=function(a){return d.enabledHours[a.format("H")]===!0},Q=function(b,c){if(!b.isValid())return!1;if(d.disabledDates&&"d"===c&&M(b))return!1;if(d.enabledDates&&"d"===c&&!N(b))return!1;if(d.minDate&&b.isBefore(d.minDate,c))return!1;if(d.maxDate&&b.isAfter(d.maxDate,c))return!1;if(d.daysOfWeekDisabled&&"d"===c&&-1!==d.daysOfWeekDisabled.indexOf(b.day()))return!1;if(d.disabledHours&&("h"===c||"m"===c||"s"===c)&&O(b))return!1;if(d.enabledHours&&("h"===c||"m"===c||"s"===c)&&!P(b))return!1;if(d.disabledTimeIntervals&&("h"===c||"m"===c||"s"===c)){var e=!1;if(a.each(d.disabledTimeIntervals,function(){return b.isBetween(this[0],this[1])?(e=!0,!1):void 0}),e)return!1}return!0},R=function(){for(var b=[],c=f.clone().startOf("y").startOf("d");c.isSame(f,"y");)b.push(a("").attr("data-action","selectMonth").addClass("month").text(c.format("MMM"))),c.add(1,"M");o.find(".datepicker-months td").empty().append(b)},S=function(){var b=o.find(".datepicker-months"),c=b.find("th"),g=b.find("tbody").find("span");c.eq(0).find("span").attr("title",d.tooltips.prevYear),c.eq(1).attr("title",d.tooltips.selectYear),c.eq(2).find("span").attr("title",d.tooltips.nextYear),b.find(".disabled").removeClass("disabled"),Q(f.clone().subtract(1,"y"),"y")||c.eq(0).addClass("disabled"),c.eq(1).text(f.year()),Q(f.clone().add(1,"y"),"y")||c.eq(2).addClass("disabled"),g.removeClass("active"),e.isSame(f,"y")&&!m&&g.eq(e.month()).addClass("active"),g.each(function(b){Q(f.clone().month(b),"M")||a(this).addClass("disabled")})},T=function(){var a=o.find(".datepicker-years"),b=a.find("th"),c=f.clone().subtract(5,"y"),g=f.clone().add(6,"y"),h="";for(b.eq(0).find("span").attr("title",d.tooltips.prevDecade),b.eq(1).attr("title",d.tooltips.selectDecade),b.eq(2).find("span").attr("title",d.tooltips.nextDecade),a.find(".disabled").removeClass("disabled"),d.minDate&&d.minDate.isAfter(c,"y")&&b.eq(0).addClass("disabled"),b.eq(1).text(c.year()+"-"+g.year()),d.maxDate&&d.maxDate.isBefore(g,"y")&&b.eq(2).addClass("disabled");!c.isAfter(g,"y");)h+=''+c.year()+"",c.add(1,"y");a.find("td").html(h)},U=function(){var a=o.find(".datepicker-decades"),c=a.find("th"),g=b({y:f.year()-f.year()%100-1}),h=g.clone().add(100,"y"),i=g.clone(),j="";for(c.eq(0).find("span").attr("title",d.tooltips.prevCentury),c.eq(2).find("span").attr("title",d.tooltips.nextCentury),a.find(".disabled").removeClass("disabled"),(g.isSame(b({y:1900}))||d.minDate&&d.minDate.isAfter(g,"y"))&&c.eq(0).addClass("disabled"),c.eq(1).text(g.year()+"-"+h.year()),(g.isSame(b({y:2e3}))||d.maxDate&&d.maxDate.isBefore(h,"y"))&&c.eq(2).addClass("disabled");!g.isAfter(h,"y");)j+=''+(g.year()+1)+" - "+(g.year()+12)+"",g.add(12,"y");j+="",a.find("td").html(j),c.eq(1).text(i.year()+1+"-"+g.year())},V=function(){var b,c,g,h,i=o.find(".datepicker-days"),j=i.find("th"),k=[];if(A()){for(j.eq(0).find("span").attr("title",d.tooltips.prevMonth),j.eq(1).attr("title",d.tooltips.selectMonth),j.eq(2).find("span").attr("title",d.tooltips.nextMonth),i.find(".disabled").removeClass("disabled"),j.eq(1).text(f.format(d.dayViewHeaderFormat)),Q(f.clone().subtract(1,"M"),"M")||j.eq(0).addClass("disabled"),Q(f.clone().add(1,"M"),"M")||j.eq(2).addClass("disabled"),b=f.clone().startOf("M").startOf("w").startOf("d"),h=0;42>h;h++)0===b.weekday()&&(c=a("
      '+b.week()+"'+b.date()+"
      '+c.format(h?"HH":"hh")+"
      '+c.format("mm")+"
      '+c.format("ss")+"

      {{ show.location }}

      {{ show.category }}

      {{ show.date|date:"l j F Y - H\hi" }}

      {{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %} - {{ show.price }}€

      {{ show.category }}

      {{ show.date|date:"l j F Y - H\hi" }}

      {{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %} - {{ show.price }} euro{{ show.price|pluralize}}

      @@ -61,7 +59,7 @@

      {{ show.title }}

      {{ show.title }}