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,
"fields": {
"name": "Pot",
"manage_entries": true,
"can_invite": true,
"guest_entry_fee": 500
}
@ -13,6 +14,7 @@
"pk": 2,
"fields": {
"name": "Soir\u00e9e de club",
"manage_entries": false,
"can_invite": false,
"guest_entry_fee": 0
}

View File

@ -1,9 +1,11 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, datetime
from datetime import timedelta
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from member.models import Club
from note.models import NoteUser, Note
@ -41,7 +43,7 @@ class GuestForm(forms.ModelForm):
def clean(self):
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."))
if not self.activity.valid:
@ -50,20 +52,21 @@ class GuestForm(forms.ModelForm):
one_year = timedelta(days=365)
qs = Guest.objects.filter(
first_name=cleaned_data["first_name"],
last_name=cleaned_data["last_name"],
first_name__iexact=cleaned_data["first_name"],
last_name__iexact=cleaned_data["last_name"],
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."))
qs = qs.filter(activity=self.activity)
if qs.exists():
self.add_error("last_name", _("This person is already invited."))
qs = Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity)
if len(qs) >= 3:
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
if "inviter" in cleaned_data:
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."))
return cleaned_data

View File

@ -1,7 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, datetime
from datetime import timedelta
from threading import Thread
from django.conf import settings
@ -27,11 +27,21 @@ class ActivityType(models.Model):
verbose_name=_('name'),
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(
verbose_name=_('can invite'),
default=False,
)
guest_entry_fee = models.PositiveIntegerField(
verbose_name=_('guest entry fee'),
default=0,
)
class Meta:
@ -236,18 +246,18 @@ class Guest(models.Model):
one_year = timedelta(days=365)
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."))
if not self.activity.valid:
raise ValidationError(_("This activity is not validated yet."))
qs = Guest.objects.filter(
first_name=self.first_name,
last_name=self.last_name,
first_name__iexact=self.first_name,
last_name__iexact=self.last_name,
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."))
qs = qs.filter(activity=self.activity)
@ -255,7 +265,7 @@ class Guest(models.Model):
raise ValidationError(_("This person is already invited."))
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."))
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
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
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)
if qs.exists():
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:
c += " table-danger"
return c

View File

@ -1,13 +1,12 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.db.models import F, Q
from django.urls import reverse_lazy
from django.utils import timezone
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2.views import SingleTableView
@ -46,12 +45,17 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
def get_context_data(self, **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(
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
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
@ -67,7 +71,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
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
@ -125,7 +129,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
.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"]
if pattern[0] != "^":
pattern = "^" + pattern
@ -148,7 +152,12 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
username=F("note__noteuser__user__username"),
note_name=F("name"),
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"))
if pattern:
note_qs = note_qs.filter(

View File

@ -54,7 +54,7 @@ class UserViewSet(ReadProtectedModelViewSet):
serializer_class = UserSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
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!

View File

@ -37,6 +37,8 @@ class ProfileForm(forms.ModelForm):
"""
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"))
def save(self, commit=True):

View File

@ -185,9 +185,9 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
Q(first_name__iregex=pattern)
| Q(last_name__iregex=pattern)
| Q(profile__section__iregex=pattern)
| Q(username__iregex="^" + pattern)
| Q(alias__iregex="^" + pattern)
| Q(normalized_alias__iregex=Alias.normalize("^" + pattern))
| Q(username__iregex=pattern)
| Q(alias__iregex=pattern)
| Q(normalized_alias__iregex=Alias.normalize(pattern))
)
else:
qs = qs.none()
@ -326,8 +326,8 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
qs = qs.filter(
Q(name__iregex=pattern)
| Q(note__alias__name__iregex="^" + pattern)
| Q(note__alias__normalized_name__iregex=Alias.normalize("^" + pattern))
| Q(note__alias__name__iregex=pattern)
| Q(note__alias__normalized_name__iregex=Alias.normalize(pattern))
)
return qs
@ -484,7 +484,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
additional_fee_renewal = 0
while c.parent_club is not None:
c = c.parent_club
if not Membership.objects.filter(
if c.membership_start and not Membership.objects.filter(
club=c,
user=user,
date_start__gte=c.membership_start,
@ -562,7 +562,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
fee = 0
c = club
while c is not None:
while c is not None and c.membership_start:
if not Membership.objects.filter(
club=c,
user=user,

View File

@ -202,8 +202,6 @@ class Transaction(PolymorphicModel):
When saving, also transfer money between two notes
"""
with transaction.atomic():
if self.pk:
self.refresh_from_db()
self.source.refresh_from_db()
self.destination.refresh_from_db()
self.validate(False)

@ -1 +1 @@
Subproject commit 3806feb67fcb1fe822cfdedddbbc4ca7eeef3829
Subproject commit 4984159a61642a0d3668e85daf39472b59b86447

View File

@ -3,8 +3,9 @@
from django.contrib import admin
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)
@ -39,3 +40,20 @@ class SogeCreditAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
# Don't create a credit manually
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(
default=timezone.now,
verbose_name=_("Place"),
verbose_name=_("Date"),
)
acquitted = models.BooleanField(
@ -82,7 +82,7 @@ class Product(models.Model):
verbose_name=_("Designation"),
)
quantity = models.IntegerField(
quantity = models.PositiveIntegerField(
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)
products = Product.objects.filter(invoice=invoice).all()
# Informations of the BDE. Should be updated when the school will move.
invoice.place = "Cachan"
invoice.place = "Gif-sur-Yvette"
invoice.my_name = "BDE ENS Cachan"
invoice.my_address_street = "61 avenue du Président Wilson"
invoice.my_city = "94230 Cachan"
invoice.my_address_street = "4 avenue des Sciences"
invoice.my_city = "91190 Gif-sur-Yvette"
invoice.bank_code = 30003
invoice.desk_code = 3894
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)
name_field = "name";
let input = target.val();
target.addClass("is-invalid");
target.removeClass("is-valid");
$("#" + prefix + "_reset").removeClass("d-none");
$.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]);
$("#" + prefix + "_pk").val(obj.id);
results_list.html("");
target.removeClass("is-invalid");
target.addClass("is-valid");
if (typeof autocompleted != 'undefined')
autocompleted(obj, prefix)
});

View File

@ -7,70 +7,7 @@
{% block content %}
<div id="activity_info" class="card bg-light shadow">
<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>
{% include "activity/activity_info.html" %}
{% if guests.data %}
<hr>

View File

@ -76,7 +76,10 @@
note: id,
guest: null
}).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);
}).fail(function(xhr) {
errMsg(xhr.responseJSON, 4000);
@ -104,7 +107,10 @@
note: target.attr("data-inviter"),
guest: id
}).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);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);

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 i18n crispy_forms_tags%}
{% 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>
{% if upcoming.data %}
{% render_table upcoming %}

View File

@ -57,6 +57,12 @@
window.document.location = $(this).data("href");
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();

View File

@ -19,10 +19,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
<input type="radio" name="transaction_type" id="type_credit">
{% trans "Credit" %}
</label>
<label type="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %}
</label>
{% if not activities_open %}
<label type="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %}
</label>
{% endif %}
{% endif %}
{% for activity in activities_open %}
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">

View File

@ -1,3 +1,6 @@
{% load escape_tex %}
{% load l10n %}
\nonstopmode
\documentclass[11pt]{article}
@ -22,14 +25,13 @@
\FPround{\prix}{#3}{2}
\FPround{\montant}{#4}{2}
\FPadd{\TotalHT}{\TotalHT}{\montant}
\eaddto\ListeProduits{#1 & \prix & #2 & \montant \cr}
}
\newcommand{\AfficheResultat}{%
\ListeProduits
\FPeval{\TotalTVA}{\TotalHT * \TVA / 100}
\FPmul{\TotalTVA}{\TotalHT}{\TVA}
\FPdiv{\TotalTVA}{\TotalTVA}{100}
\FPadd{\TotalTTC}{\TotalHT}{\TotalTVA}
\FPround{\TotalHT}{\TotalHT}{2}
\FPround{\TotalTVA}{\TotalTVA}{2}
@ -45,15 +47,14 @@
\textbf{Total TTC} & & & \TotalTTC
}
\newcommand*\eaddto[2]{% version développée de \addto
\edef\tmp{#2}%
\expandafter\addto
\expandafter#1%
\expandafter{\tmp}%
\newcommand {\ListeProduits}{
{% localize off %}
{% for product in products %}
{{ 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
{% endfor %}
{% endlocalize %}
}
\newcommand {\ListeProduits}{}
% Logo du BDE
\AddToShipoutPicture*{
\put(0,0){
@ -81,22 +82,22 @@
\def\IBAN{FR76\CodeBanque\CodeGuichet\NCompte\CleRib}
\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\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\FactureObjet {{"{"}}{{ obj.object|safe }} } % Objet du document
\def\FactureObjet {{"{"}}{{ obj.object|safe|escape_tex }} } % Objet du document
% Description de la facture
\def\FactureDescr {{"{"}}{{ obj.description|safe }}}
\def\FactureDescr {{"{"}}{{ obj.description|safe|escape_tex }}}
% Infos Client
\def\ClientNom{{"{"}}{{obj.name|safe}}} % Nom du client
\def\ClientAdresse{{"{"}}{{ obj.address|safe }}} % Adresse du client
\def\ClientNom{{"{"}}{{ obj.name|safe|escape_tex }}} % Nom du client
\def\ClientAdresse{{"{"}}{{ obj.address|safe|escape_tex }}} % Adresse du client
% Liste des produits facturés : Désignation, quantité, prix unitaire HT
{% 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 %}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%