Compare commits

...

19 Commits

Author SHA1 Message Date
Yohann D'ANELLO e23eafd56c Add invoices in Django Admin 2020-08-06 21:51:53 +02:00
Yohann D'ANELLO 3e28ed8716 Remove space in IBAN 2020-08-06 21:45:50 +02:00
Yohann D'ANELLO 5c01c0bb6c Display 2 decimals in invoices 2020-08-06 21:02:25 +02:00
Yohann D'ANELLO 979628b02d 🐛 default last_report date is today, not the migration date 2020-08-06 20:30:17 +02:00
Yohann D'ANELLO bb8e3aaccf Display what is matched in user table (experimental, normalizing is not working) 2020-08-06 20:21:35 +02:00
Yohann D'ANELLO 86ff23357c Display what is matched in user table (experimental, normalizing is not working) 2020-08-06 20:19:29 +02:00
Yohann D'ANELLO fd2f426f55 🐛 Fix signup 2020-08-06 19:56:37 +02:00
Yohann D'ANELLO 48a7128370 📦 On a déménagé 2020-08-06 19:42:22 +02:00
Yohann D'ANELLO f222ba134d 🐛 Remove \eaddto in the invoice template, unicode characters weren't supported 2020-08-06 19:39:40 +02:00
Yohann D'ANELLO d95cd8c7c7 🎨 Better autocomplete field 2020-08-06 18:27:57 +02:00
Yohann D'ANELLO 5b3361f086 Product quantity must be positive 2020-08-06 18:05:58 +02:00
Yohann D'ANELLO 9c7cb07dec Improve activity interface 2020-08-06 17:41:30 +02:00
Yohann D'ANELLO dd4b24d999 Don't match users only with the start of the name 2020-08-06 15:21:16 +02:00
Yohann D'ANELLO eb3d426947 💩 Don't reset a transaction before saving it... 2020-08-06 15:18:14 +02:00
Yohann D'ANELLO d8c7018b9a FP is bugggy 2020-08-06 15:08:35 +02:00
Yohann D'ANELLO f47a0b8c9d Use neg for negative numbers in invoices 2020-08-06 14:41:14 +02:00
Yohann D'ANELLO c859fc7821 Use neg for negative numbers in invoices 2020-08-06 14:39:01 +02:00
Yohann D'ANELLO a4702fca86 Escape special TeX characters 2020-08-06 14:19:51 +02:00
Yohann D'ANELLO de5e0c958e Fix some activity errors 2020-08-06 14:11:55 +02:00
24 changed files with 889 additions and 740 deletions

View File

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

View File

@ -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,19 +52,20 @@ 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,6 +76,9 @@
note: id, note: id,
guest: null guest: null
}).done(function () { }).done(function () {
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); addMsg("Entrée effectuée !", "success", 4000);
reloadTable(true); reloadTable(true);
}).fail(function(xhr) { }).fail(function(xhr) {
@ -104,6 +107,9 @@
note: target.attr("data-inviter"), note: target.attr("data-inviter"),
guest: id guest: id
}).done(function () { }).done(function () {
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); addMsg("Entrée effectuée !", "success", 4000);
reloadTable(true); reloadTable(true);
}).fail(function (xhr) { }).fail(function (xhr) {

View File

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

View File

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

View File

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

View File

@ -19,11 +19,13 @@ 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>
{% if not activities_open %}
<label type="type_debit" class="btn btn-sm btn-outline-primary"> <label type="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit"> <input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %} {% trans "Debit" %}
</label> </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">
{% trans "Entries" %} {{ activity.name }} {% trans "Entries" %} {{ activity.name }}

View File

@ -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){
@ -83,20 +84,20 @@
\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 %}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%