mirror of https://gitlab.crans.org/bde/nk20
Compare commits
19 Commits
434a393f3b
...
e23eafd56c
Author | SHA1 | Date |
---|---|---|
Yohann D'ANELLO | e23eafd56c | |
Yohann D'ANELLO | 3e28ed8716 | |
Yohann D'ANELLO | 5c01c0bb6c | |
Yohann D'ANELLO | 979628b02d | |
Yohann D'ANELLO | bb8e3aaccf | |
Yohann D'ANELLO | 86ff23357c | |
Yohann D'ANELLO | fd2f426f55 | |
Yohann D'ANELLO | 48a7128370 | |
Yohann D'ANELLO | f222ba134d | |
Yohann D'ANELLO | d95cd8c7c7 | |
Yohann D'ANELLO | 5b3361f086 | |
Yohann D'ANELLO | 9c7cb07dec | |
Yohann D'ANELLO | dd4b24d999 | |
Yohann D'ANELLO | eb3d426947 | |
Yohann D'ANELLO | d8c7018b9a | |
Yohann D'ANELLO | f47a0b8c9d | |
Yohann D'ANELLO | c859fc7821 | |
Yohann D'ANELLO | a4702fca86 | |
Yohann D'ANELLO | de5e0c958e |
|
@ -4,6 +4,7 @@
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Pot",
|
"name": "Pot",
|
||||||
|
"manage_entries": true,
|
||||||
"can_invite": true,
|
"can_invite": true,
|
||||||
"guest_entry_fee": 500
|
"guest_entry_fee": 500
|
||||||
}
|
}
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Soir\u00e9e de club",
|
"name": "Soir\u00e9e de club",
|
||||||
|
"manage_entries": false,
|
||||||
"can_invite": false,
|
"can_invite": false,
|
||||||
"guest_entry_fee": 0
|
"guest_entry_fee": 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from datetime import timedelta, datetime
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from member.models import Club
|
from member.models import Club
|
||||||
from note.models import NoteUser, Note
|
from note.models import NoteUser, Note
|
||||||
|
@ -41,7 +43,7 @@ class GuestForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
if self.activity.date_start > datetime.now():
|
if timezone.now() > timezone.localtime(self.activity.date_start):
|
||||||
self.add_error("inviter", _("You can't invite someone once the activity is started."))
|
self.add_error("inviter", _("You can't invite someone once the activity is started."))
|
||||||
|
|
||||||
if not self.activity.valid:
|
if not self.activity.valid:
|
||||||
|
@ -50,20 +52,21 @@ class GuestForm(forms.ModelForm):
|
||||||
one_year = timedelta(days=365)
|
one_year = timedelta(days=365)
|
||||||
|
|
||||||
qs = Guest.objects.filter(
|
qs = Guest.objects.filter(
|
||||||
first_name=cleaned_data["first_name"],
|
first_name__iexact=cleaned_data["first_name"],
|
||||||
last_name=cleaned_data["last_name"],
|
last_name__iexact=cleaned_data["last_name"],
|
||||||
activity__date_start__gte=self.activity.date_start - one_year,
|
activity__date_start__gte=self.activity.date_start - one_year,
|
||||||
|
entry__isnull=False,
|
||||||
)
|
)
|
||||||
if len(qs) >= 5:
|
if qs.count() >= 5:
|
||||||
self.add_error("last_name", _("This person has been already invited 5 times this year."))
|
self.add_error("last_name", _("This person has been already invited 5 times this year."))
|
||||||
|
|
||||||
qs = qs.filter(activity=self.activity)
|
qs = qs.filter(activity=self.activity)
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
self.add_error("last_name", _("This person is already invited."))
|
self.add_error("last_name", _("This person is already invited."))
|
||||||
|
|
||||||
qs = Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity)
|
if "inviter" in cleaned_data:
|
||||||
if len(qs) >= 3:
|
if Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity).count() >= 3:
|
||||||
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
|
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -27,11 +27,21 @@ class ActivityType(models.Model):
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
manage_entries = models.BooleanField(
|
||||||
|
verbose_name=_('manage entries'),
|
||||||
|
help_text=_('Enable the support of entries for this activity.'),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
can_invite = models.BooleanField(
|
can_invite = models.BooleanField(
|
||||||
verbose_name=_('can invite'),
|
verbose_name=_('can invite'),
|
||||||
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
guest_entry_fee = models.PositiveIntegerField(
|
guest_entry_fee = models.PositiveIntegerField(
|
||||||
verbose_name=_('guest entry fee'),
|
verbose_name=_('guest entry fee'),
|
||||||
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -236,18 +246,18 @@ class Guest(models.Model):
|
||||||
one_year = timedelta(days=365)
|
one_year = timedelta(days=365)
|
||||||
|
|
||||||
if not force_insert:
|
if not force_insert:
|
||||||
if self.activity.date_start > datetime.now():
|
if timezone.now() > timezone.localtime(self.activity.date_start):
|
||||||
raise ValidationError(_("You can't invite someone once the activity is started."))
|
raise ValidationError(_("You can't invite someone once the activity is started."))
|
||||||
|
|
||||||
if not self.activity.valid:
|
if not self.activity.valid:
|
||||||
raise ValidationError(_("This activity is not validated yet."))
|
raise ValidationError(_("This activity is not validated yet."))
|
||||||
|
|
||||||
qs = Guest.objects.filter(
|
qs = Guest.objects.filter(
|
||||||
first_name=self.first_name,
|
first_name__iexact=self.first_name,
|
||||||
last_name=self.last_name,
|
last_name__iexact=self.last_name,
|
||||||
activity__date_start__gte=self.activity.date_start - one_year,
|
activity__date_start__gte=self.activity.date_start - one_year,
|
||||||
)
|
)
|
||||||
if len(qs) >= 5:
|
if qs.count() >= 5:
|
||||||
raise ValidationError(_("This person has been already invited 5 times this year."))
|
raise ValidationError(_("This person has been already invited 5 times this year."))
|
||||||
|
|
||||||
qs = qs.filter(activity=self.activity)
|
qs = qs.filter(activity=self.activity)
|
||||||
|
@ -255,7 +265,7 @@ class Guest(models.Model):
|
||||||
raise ValidationError(_("This person is already invited."))
|
raise ValidationError(_("This person is already invited."))
|
||||||
|
|
||||||
qs = Guest.objects.filter(inviter=self.inviter, activity=self.activity)
|
qs = Guest.objects.filter(inviter=self.inviter, activity=self.activity)
|
||||||
if len(qs) >= 3:
|
if qs.count() >= 3:
|
||||||
raise ValidationError(_("You can't invite more than 3 people to this activity."))
|
raise ValidationError(_("You can't invite more than 3 people to this activity."))
|
||||||
|
|
||||||
return super().save(force_insert, force_update, using, update_fields)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
@ -66,6 +66,10 @@ def get_row_class(record):
|
||||||
qs = Entry.objects.filter(note=record.note, activity=record.activity, guest=None)
|
qs = Entry.objects.filter(note=record.note, activity=record.activity, guest=None)
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
c += " table-success"
|
c += " table-success"
|
||||||
|
elif not record.note.user.memberships.filter(club=record.activity.attendees_club,
|
||||||
|
date_start__lte=timezone.now(),
|
||||||
|
date_end__gte=timezone.now()).exists():
|
||||||
|
c += " table-info"
|
||||||
elif record.note.balance < 0:
|
elif record.note.balance < 0:
|
||||||
c += " table-danger"
|
c += " table-danger"
|
||||||
return c
|
return c
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils import timezone
|
||||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
|
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
|
@ -46,12 +45,17 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
upcoming_activities = Activity.objects.filter(date_end__gt=datetime.now())
|
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
|
||||||
context['upcoming'] = ActivityTable(
|
context['upcoming'] = ActivityTable(
|
||||||
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
|
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
|
||||||
prefix='upcoming-',
|
prefix='upcoming-',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
started_activities = Activity.objects\
|
||||||
|
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||||
|
.filter(open=True, valid=True).all()
|
||||||
|
context["started_activities"] = started_activities
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
|
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
|
||||||
context["guests"] = table
|
context["guests"] = table
|
||||||
|
|
||||||
context["activity_started"] = datetime.now(timezone.utc) > self.object.date_start
|
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -125,7 +129,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
||||||
.order_by('last_name', 'first_name').distinct()
|
.order_by('last_name', 'first_name').distinct()
|
||||||
|
|
||||||
if "search" in self.request.GET:
|
if "search" in self.request.GET and self.request.GET["search"]:
|
||||||
pattern = self.request.GET["search"]
|
pattern = self.request.GET["search"]
|
||||||
if pattern[0] != "^":
|
if pattern[0] != "^":
|
||||||
pattern = "^" + pattern
|
pattern = "^" + pattern
|
||||||
|
@ -148,7 +152,12 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
username=F("note__noteuser__user__username"),
|
username=F("note__noteuser__user__username"),
|
||||||
note_name=F("name"),
|
note_name=F("name"),
|
||||||
balance=F("note__balance"))\
|
balance=F("note__balance"))\
|
||||||
.filter(note__polymorphic_ctype__model="noteuser")\
|
.filter(note__noteuser__isnull=False)\
|
||||||
|
.filter(
|
||||||
|
note__noteuser__user__memberships__club=activity.attendees_club,
|
||||||
|
note__noteuser__user__memberships__date_start__lte=timezone.now(),
|
||||||
|
note__noteuser__user__memberships__date_end__gte=timezone.now(),
|
||||||
|
)\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
||||||
if pattern:
|
if pattern:
|
||||||
note_qs = note_qs.filter(
|
note_qs = note_qs.filter(
|
||||||
|
|
|
@ -54,7 +54,7 @@ class UserViewSet(ReadProtectedModelViewSet):
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
|
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
|
||||||
search_fields = ['$username', '$first_name', '$last_name', ]
|
search_fields = ['$username', '$first_name', '$last_name', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||||
|
|
||||||
|
|
||||||
# This ViewSet is the only one that is accessible from all authenticated users!
|
# This ViewSet is the only one that is accessible from all authenticated users!
|
||||||
|
|
|
@ -37,6 +37,8 @@ class ProfileForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
A form for the extras field provided by the :model:`member.Profile` model.
|
A form for the extras field provided by the :model:`member.Profile` model.
|
||||||
"""
|
"""
|
||||||
|
report_frequency = forms.IntegerField(required=False, initial=0, label=_("Report frequency"))
|
||||||
|
|
||||||
last_report = forms.DateField(required=False, disabled=True, label=_("Last report date"))
|
last_report = forms.DateField(required=False, disabled=True, label=_("Last report date"))
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
|
|
@ -185,9 +185,9 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
Q(first_name__iregex=pattern)
|
Q(first_name__iregex=pattern)
|
||||||
| Q(last_name__iregex=pattern)
|
| Q(last_name__iregex=pattern)
|
||||||
| Q(profile__section__iregex=pattern)
|
| Q(profile__section__iregex=pattern)
|
||||||
| Q(username__iregex="^" + pattern)
|
| Q(username__iregex=pattern)
|
||||||
| Q(alias__iregex="^" + pattern)
|
| Q(alias__iregex=pattern)
|
||||||
| Q(normalized_alias__iregex=Alias.normalize("^" + pattern))
|
| Q(normalized_alias__iregex=Alias.normalize(pattern))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
qs = qs.none()
|
qs = qs.none()
|
||||||
|
@ -326,8 +326,8 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
|
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
Q(name__iregex=pattern)
|
Q(name__iregex=pattern)
|
||||||
| Q(note__alias__name__iregex="^" + pattern)
|
| Q(note__alias__name__iregex=pattern)
|
||||||
| Q(note__alias__normalized_name__iregex=Alias.normalize("^" + pattern))
|
| Q(note__alias__normalized_name__iregex=Alias.normalize(pattern))
|
||||||
)
|
)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
@ -484,7 +484,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||||
additional_fee_renewal = 0
|
additional_fee_renewal = 0
|
||||||
while c.parent_club is not None:
|
while c.parent_club is not None:
|
||||||
c = c.parent_club
|
c = c.parent_club
|
||||||
if not Membership.objects.filter(
|
if c.membership_start and not Membership.objects.filter(
|
||||||
club=c,
|
club=c,
|
||||||
user=user,
|
user=user,
|
||||||
date_start__gte=c.membership_start,
|
date_start__gte=c.membership_start,
|
||||||
|
@ -562,7 +562,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||||
|
|
||||||
fee = 0
|
fee = 0
|
||||||
c = club
|
c = club
|
||||||
while c is not None:
|
while c is not None and c.membership_start:
|
||||||
if not Membership.objects.filter(
|
if not Membership.objects.filter(
|
||||||
club=c,
|
club=c,
|
||||||
user=user,
|
user=user,
|
||||||
|
|
|
@ -202,8 +202,6 @@ class Transaction(PolymorphicModel):
|
||||||
When saving, also transfer money between two notes
|
When saving, also transfer money between two notes
|
||||||
"""
|
"""
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if self.pk:
|
|
||||||
self.refresh_from_db()
|
|
||||||
self.source.refresh_from_db()
|
self.source.refresh_from_db()
|
||||||
self.destination.refresh_from_db()
|
self.destination.refresh_from_db()
|
||||||
self.validate(False)
|
self.validate(False)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3806feb67fcb1fe822cfdedddbbc4ca7eeef3829
|
Subproject commit 4984159a61642a0d3668e85daf39472b59b86447
|
|
@ -3,8 +3,9 @@
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from note_kfet.admin import admin_site
|
from note_kfet.admin import admin_site
|
||||||
|
from .forms import ProductForm
|
||||||
|
|
||||||
from .models import RemittanceType, Remittance, SogeCredit
|
from .models import RemittanceType, Remittance, SogeCredit, Invoice, Product
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RemittanceType, site=admin_site)
|
@admin.register(RemittanceType, site=admin_site)
|
||||||
|
@ -39,3 +40,20 @@ class SogeCreditAdmin(admin.ModelAdmin):
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request):
|
||||||
# Don't create a credit manually
|
# Don't create a credit manually
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ProductInline(admin.StackedInline):
|
||||||
|
"""
|
||||||
|
Inline product in invoice admin
|
||||||
|
"""
|
||||||
|
model = Product
|
||||||
|
form = ProductForm
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Invoice, site=admin_site)
|
||||||
|
class InvoiceAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin customisation for Invoice
|
||||||
|
"""
|
||||||
|
list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',)
|
||||||
|
inlines = (ProductInline,)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class Invoice(models.Model):
|
||||||
|
|
||||||
date = models.DateField(
|
date = models.DateField(
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
verbose_name=_("Place"),
|
verbose_name=_("Date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
acquitted = models.BooleanField(
|
acquitted = models.BooleanField(
|
||||||
|
@ -82,7 +82,7 @@ class Product(models.Model):
|
||||||
verbose_name=_("Designation"),
|
verbose_name=_("Designation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = models.IntegerField(
|
quantity = models.PositiveIntegerField(
|
||||||
verbose_name=_("Quantity")
|
verbose_name=_("Quantity")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
|
||||||
|
def do_latex_escape(value):
|
||||||
|
return (
|
||||||
|
value.replace("&", "\\&")
|
||||||
|
.replace("$", "\\$")
|
||||||
|
.replace("%", "\\%")
|
||||||
|
.replace("#", "\\#")
|
||||||
|
.replace("_", "\\_")
|
||||||
|
.replace("{", "\\{")
|
||||||
|
.replace("}", "\\}")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
register.filter("escape_tex", do_latex_escape)
|
|
@ -141,11 +141,10 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
||||||
invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request.user, Invoice, "view")).get(pk=pk)
|
invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request.user, Invoice, "view")).get(pk=pk)
|
||||||
products = Product.objects.filter(invoice=invoice).all()
|
products = Product.objects.filter(invoice=invoice).all()
|
||||||
|
|
||||||
# Informations of the BDE. Should be updated when the school will move.
|
invoice.place = "Gif-sur-Yvette"
|
||||||
invoice.place = "Cachan"
|
|
||||||
invoice.my_name = "BDE ENS Cachan"
|
invoice.my_name = "BDE ENS Cachan"
|
||||||
invoice.my_address_street = "61 avenue du Président Wilson"
|
invoice.my_address_street = "4 avenue des Sciences"
|
||||||
invoice.my_city = "94230 Cachan"
|
invoice.my_city = "91190 Gif-sur-Yvette"
|
||||||
invoice.bank_code = 30003
|
invoice.bank_code = 30003
|
||||||
invoice.desk_code = 3894
|
invoice.desk_code = 3894
|
||||||
invoice.account_number = 37280662
|
invoice.account_number = 37280662
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,8 @@ $(document).ready(function () {
|
||||||
if (!name_field)
|
if (!name_field)
|
||||||
name_field = "name";
|
name_field = "name";
|
||||||
let input = target.val();
|
let input = target.val();
|
||||||
|
target.addClass("is-invalid");
|
||||||
|
target.removeClass("is-valid");
|
||||||
$("#" + prefix + "_reset").removeClass("d-none");
|
$("#" + prefix + "_reset").removeClass("d-none");
|
||||||
|
|
||||||
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
||||||
|
@ -27,6 +29,10 @@ $(document).ready(function () {
|
||||||
target.val(obj[name_field]);
|
target.val(obj[name_field]);
|
||||||
$("#" + prefix + "_pk").val(obj.id);
|
$("#" + prefix + "_pk").val(obj.id);
|
||||||
|
|
||||||
|
results_list.html("");
|
||||||
|
target.removeClass("is-invalid");
|
||||||
|
target.addClass("is-valid");
|
||||||
|
|
||||||
if (typeof autocompleted != 'undefined')
|
if (typeof autocompleted != 'undefined')
|
||||||
autocompleted(obj, prefix)
|
autocompleted(obj, prefix)
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,70 +7,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div id="activity_info" class="card bg-light shadow">
|
{% include "activity/activity_info.html" %}
|
||||||
<div class="card-header text-center">
|
|
||||||
<h4>{{ activity.name }}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body" id="profile_infos">
|
|
||||||
<dl class="row">
|
|
||||||
<dt class="col-xl-6">{% trans 'description'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6"> {{ activity.description }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'type'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6"> {{ activity.activity_type }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'start date'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.date_start }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'end date'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.date_end }}</dd>
|
|
||||||
|
|
||||||
{% if ".view_"|has_perm:activity.creater %}
|
|
||||||
<dt class="col-xl-6">{% trans 'creater'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6"><a href="{% url "member:user_detail" pk=activity.creater.pk %}">{{ activity.creater }}</a></dd>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'organizer'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.organizer.pk %}">{{ activity.organizer }}</a></dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'attendees club'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd>
|
|
||||||
|
|
||||||
{% if activity.activity_type.can_invite %}
|
|
||||||
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-footer text-center">
|
|
||||||
{% if activity.open and ".change__open"|has_perm:activity %}
|
|
||||||
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if activity.valid and ".change__open"|has_perm:activity %}
|
|
||||||
<a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if not activity.open and ".change__valid"|has_perm:activity %}
|
|
||||||
<a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if ".change_"|has_perm:activity %}
|
|
||||||
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if activity.activity_type.can_invite and not activity_started %}
|
|
||||||
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}"> {% trans "Invite" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if guests.data %}
|
{% if guests.data %}
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -76,7 +76,10 @@
|
||||||
note: id,
|
note: id,
|
||||||
guest: null
|
guest: null
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg("Entrée effectuée !", "success", 4000);
|
if (target.hasClass("table-info"))
|
||||||
|
addMsg("Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.", "warning", 10000);
|
||||||
|
else
|
||||||
|
addMsg("Entrée effectuée !", "success", 4000);
|
||||||
reloadTable(true);
|
reloadTable(true);
|
||||||
}).fail(function(xhr) {
|
}).fail(function(xhr) {
|
||||||
errMsg(xhr.responseJSON, 4000);
|
errMsg(xhr.responseJSON, 4000);
|
||||||
|
@ -104,7 +107,10 @@
|
||||||
note: target.attr("data-inviter"),
|
note: target.attr("data-inviter"),
|
||||||
guest: id
|
guest: id
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg("Entrée effectuée !", "success", 4000);
|
if (target.hasClass("table-info"))
|
||||||
|
addMsg("Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.", "warning", 10000);
|
||||||
|
else
|
||||||
|
addMsg("Entrée effectuée !", "success", 4000);
|
||||||
reloadTable(true);
|
reloadTable(true);
|
||||||
}).fail(function (xhr) {
|
}).fail(function (xhr) {
|
||||||
errMsg(xhr.responseJSON, 4000);
|
errMsg(xhr.responseJSON, 4000);
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load perms %}
|
||||||
|
{% load pretty_money %}
|
||||||
|
|
||||||
|
{% url 'activity:activity_detail' activity.pk as activity_detail_url %}
|
||||||
|
|
||||||
|
<div id="activity_info" class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4>
|
||||||
|
{% if request.path_info != activity_detail_url %}
|
||||||
|
<a href="{{ activity_detail_url }}">{{ activity.name }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ activity.name }}
|
||||||
|
{% endif %}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="profile_infos">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-xl-6">{% trans 'description'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"> {{ activity.description }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'type'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"> {{ activity.activity_type }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'start date'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.date_start }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'end date'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.date_end }}</dd>
|
||||||
|
|
||||||
|
{% if ".view_"|has_perm:activity.creater %}
|
||||||
|
<dt class="col-xl-6">{% trans 'creater'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"><a href="{% url "member:user_detail" pk=activity.creater.pk %}">{{ activity.creater }}</a></dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'organizer'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.organizer.pk %}">{{ activity.organizer }}</a></dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'attendees club'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd>
|
||||||
|
|
||||||
|
{% if activity.activity_type.can_invite %}
|
||||||
|
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
{% if activity.open and activity.activity_type.manage_entries and ".change__open"|has_perm:activity %}
|
||||||
|
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if request.path_info == activity_detail_url %}
|
||||||
|
{% if activity.valid and ".change__open"|has_perm:activity %}
|
||||||
|
<a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if not activity.open and ".change__valid"|has_perm:activity %}
|
||||||
|
<a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if ".change_"|has_perm:activity %}
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if activity.activity_type.can_invite and not activity_started %}
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}"> {% trans "Invite" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -2,6 +2,14 @@
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load i18n crispy_forms_tags%}
|
{% load i18n crispy_forms_tags%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if started_activities %}
|
||||||
|
<h2>{% trans "Current activity" %}</h2>
|
||||||
|
{% for activity in started_activities %}
|
||||||
|
{% include "activity/activity_info.html" %}
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h2>{% trans "Upcoming activities" %}</h2>
|
<h2>{% trans "Upcoming activities" %}</h2>
|
||||||
{% if upcoming.data %}
|
{% if upcoming.data %}
|
||||||
{% render_table upcoming %}
|
{% render_table upcoming %}
|
||||||
|
|
|
@ -57,6 +57,12 @@
|
||||||
window.document.location = $(this).data("href");
|
window.document.location = $(this).data("href");
|
||||||
timer_on = false;
|
timer_on = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("tr").each(function() {
|
||||||
|
$(this).find("td:eq(0), td:eq(1), td:eq(2), td:eq(3), td:eq(5)").each(function() {
|
||||||
|
$(this).html($(this).text().replace(new RegExp(searchbar_obj.val(), 'i'), "<mark>$&</mark>"));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
|
@ -19,10 +19,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
<input type="radio" name="transaction_type" id="type_credit">
|
<input type="radio" name="transaction_type" id="type_credit">
|
||||||
{% trans "Credit" %}
|
{% trans "Credit" %}
|
||||||
</label>
|
</label>
|
||||||
<label type="type_debit" class="btn btn-sm btn-outline-primary">
|
{% if not activities_open %}
|
||||||
<input type="radio" name="transaction_type" id="type_debit">
|
<label type="type_debit" class="btn btn-sm btn-outline-primary">
|
||||||
{% trans "Debit" %}
|
<input type="radio" name="transaction_type" id="type_debit">
|
||||||
</label>
|
{% trans "Debit" %}
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for activity in activities_open %}
|
{% for activity in activities_open %}
|
||||||
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">
|
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
{% load escape_tex %}
|
||||||
|
{% load l10n %}
|
||||||
|
|
||||||
\nonstopmode
|
\nonstopmode
|
||||||
\documentclass[11pt]{article}
|
\documentclass[11pt]{article}
|
||||||
|
|
||||||
|
@ -22,14 +25,13 @@
|
||||||
\FPround{\prix}{#3}{2}
|
\FPround{\prix}{#3}{2}
|
||||||
\FPround{\montant}{#4}{2}
|
\FPround{\montant}{#4}{2}
|
||||||
\FPadd{\TotalHT}{\TotalHT}{\montant}
|
\FPadd{\TotalHT}{\TotalHT}{\montant}
|
||||||
|
|
||||||
\eaddto\ListeProduits{#1 & \prix & #2 & \montant \cr}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\newcommand{\AfficheResultat}{%
|
\newcommand{\AfficheResultat}{%
|
||||||
\ListeProduits
|
\ListeProduits
|
||||||
|
|
||||||
\FPeval{\TotalTVA}{\TotalHT * \TVA / 100}
|
\FPmul{\TotalTVA}{\TotalHT}{\TVA}
|
||||||
|
\FPdiv{\TotalTVA}{\TotalTVA}{100}
|
||||||
\FPadd{\TotalTTC}{\TotalHT}{\TotalTVA}
|
\FPadd{\TotalTTC}{\TotalHT}{\TotalTVA}
|
||||||
\FPround{\TotalHT}{\TotalHT}{2}
|
\FPround{\TotalHT}{\TotalHT}{2}
|
||||||
\FPround{\TotalTVA}{\TotalTVA}{2}
|
\FPround{\TotalTVA}{\TotalTVA}{2}
|
||||||
|
@ -45,15 +47,14 @@
|
||||||
\textbf{Total TTC} & & & \TotalTTC
|
\textbf{Total TTC} & & & \TotalTTC
|
||||||
}
|
}
|
||||||
|
|
||||||
\newcommand*\eaddto[2]{% version développée de \addto
|
\newcommand {\ListeProduits}{
|
||||||
\edef\tmp{#2}%
|
{% localize off %}
|
||||||
\expandafter\addto
|
{% for product in products %}
|
||||||
\expandafter#1%
|
{{ product.designation|safe|escape_tex }} & {{ product.amount_euros|safe|escape_tex|floatformat:2 }} & {{ product.quantity|safe|escape_tex|floatformat:2 }} & {{ product.total_euros|safe|escape_tex|floatformat:2 }} \cr
|
||||||
\expandafter{\tmp}%
|
{% endfor %}
|
||||||
|
{% endlocalize %}
|
||||||
}
|
}
|
||||||
|
|
||||||
\newcommand {\ListeProduits}{}
|
|
||||||
|
|
||||||
% Logo du BDE
|
% Logo du BDE
|
||||||
\AddToShipoutPicture*{
|
\AddToShipoutPicture*{
|
||||||
\put(0,0){
|
\put(0,0){
|
||||||
|
@ -81,22 +82,22 @@
|
||||||
\def\IBAN{FR76\CodeBanque\CodeGuichet\NCompte\CleRib}
|
\def\IBAN{FR76\CodeBanque\CodeGuichet\NCompte\CleRib}
|
||||||
\def\CodeBic{{"{"}}{{ obj.bic }}}
|
\def\CodeBic{{"{"}}{{ obj.bic }}}
|
||||||
|
|
||||||
\def\FactureNum {{"{"}}{{obj.id}}} % Numéro de facture
|
\def\FactureNum {{"{"}}{{ obj.id }}} % Numéro de facture
|
||||||
\def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non
|
\def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non
|
||||||
\def\FactureLieu {{"{"}}{{ obj.place }}} % Lieu de l'édition de la facture
|
\def\FactureLieu {{"{"}}{{ obj.place|safe|escape_tex }}} % Lieu de l'édition de la facture
|
||||||
\def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture
|
\def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture
|
||||||
\def\FactureObjet {{"{"}}{{ obj.object|safe }} } % Objet du document
|
\def\FactureObjet {{"{"}}{{ obj.object|safe|escape_tex }} } % Objet du document
|
||||||
% Description de la facture
|
% Description de la facture
|
||||||
\def\FactureDescr {{"{"}}{{ obj.description|safe }}}
|
\def\FactureDescr {{"{"}}{{ obj.description|safe|escape_tex }}}
|
||||||
|
|
||||||
% Infos Client
|
% Infos Client
|
||||||
\def\ClientNom{{"{"}}{{obj.name|safe}}} % Nom du client
|
\def\ClientNom{{"{"}}{{ obj.name|safe|escape_tex }}} % Nom du client
|
||||||
\def\ClientAdresse{{"{"}}{{ obj.address|safe }}} % Adresse du client
|
\def\ClientAdresse{{"{"}}{{ obj.address|safe|escape_tex }}} % Adresse du client
|
||||||
|
|
||||||
% Liste des produits facturés : Désignation, quantité, prix unitaire HT
|
% Liste des produits facturés : Désignation, quantité, prix unitaire HT
|
||||||
|
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
\AjouterProduit{ {{product.designation|safe}}} { {{product.quantity|safe}}} { {{product.amount_euros|safe}}} { {{product.total_euros|safe}}}
|
\AjouterProduit{{"{"}}{{ product.designation|safe|escape_tex }}} {{"{"}}{{ product.quantity|safe|escape_tex }}} {{"{"}}{{ product.amount_euros|safe|escape_tex }}} {{"{"}}{{ product.total_euros|safe|escape_tex }}}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
Loading…
Reference in New Issue