mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-11-27 02:43:01 +00:00
Merge branch 'master' into tranfer_front
This commit is contained in:
commit
6ea59a4603
@ -9,6 +9,11 @@ RUN apt update && \
|
|||||||
apt install -y gettext nginx uwsgi uwsgi-plugin-python3 && \
|
apt install -y gettext nginx uwsgi uwsgi-plugin-python3 && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install LaTeX requirements
|
||||||
|
RUN apt update && \
|
||||||
|
apt install -y texlive-latex-extra texlive-fonts-extra texlive-lang-french && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY . /code/
|
COPY . /code/
|
||||||
|
|
||||||
# Comment what is not needed
|
# Comment what is not needed
|
||||||
|
@ -6,13 +6,17 @@
|
|||||||
|
|
||||||
## Installation sur un serveur
|
## Installation sur un serveur
|
||||||
|
|
||||||
On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout nu ou bien configuré.
|
On supposera pour la suite que vous utilisez Debian/Ubuntu sur un serveur tout nu ou bien configuré.
|
||||||
|
|
||||||
1. Paquets nécessaires
|
1. Paquets nécessaires
|
||||||
|
|
||||||
$ sudo apt install nginx python3 python3-pip python3-dev uwsgi
|
$ sudo apt install nginx python3 python3-pip python3-dev uwsgi
|
||||||
$ sudo apt install uwsgi-plugin-python3 python3-venv git acl
|
$ sudo apt install uwsgi-plugin-python3 python3-venv git acl
|
||||||
|
|
||||||
|
La génération des factures de l'application trésorerie nécessite une installation de LaTeX suffisante :
|
||||||
|
|
||||||
|
$ sudo apt install texlive-latex-extra texlive-fonts-extra texlive-lang-french
|
||||||
|
|
||||||
2. Clonage du dépot
|
2. Clonage du dépot
|
||||||
|
|
||||||
on se met au bon endroit :
|
on se met au bon endroit :
|
||||||
|
@ -12,6 +12,7 @@ from activity.api.urls import register_activity_urls
|
|||||||
from api.viewsets import ReadProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
from member.api.urls import register_members_urls
|
from member.api.urls import register_members_urls
|
||||||
from note.api.urls import register_note_urls
|
from note.api.urls import register_note_urls
|
||||||
|
from treasury.api.urls import register_treasury_urls
|
||||||
from logs.api.urls import register_logs_urls
|
from logs.api.urls import register_logs_urls
|
||||||
from permission.api.urls import register_permission_urls
|
from permission.api.urls import register_permission_urls
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ router.register('user', UserViewSet)
|
|||||||
register_members_urls(router, 'members')
|
register_members_urls(router, 'members')
|
||||||
register_activity_urls(router, 'activity')
|
register_activity_urls(router, 'activity')
|
||||||
register_note_urls(router, 'note')
|
register_note_urls(router, 'note')
|
||||||
|
register_treasury_urls(router, 'treasury')
|
||||||
register_permission_urls(router, 'permission')
|
register_permission_urls(router, 'permission')
|
||||||
register_logs_urls(router, 'logs')
|
register_logs_urls(router, 'logs')
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "BDE",
|
"name": "BDE",
|
||||||
"email": "tresorerie.bde@example.com",
|
"email": "tresorerie.bde@example.com",
|
||||||
"membership_fee": 5,
|
"membership_fee": 500,
|
||||||
"membership_duration": "396 00:00:00",
|
"membership_duration": "396 00:00:00",
|
||||||
"membership_start": "213 00:00:00",
|
"membership_start": "213 00:00:00",
|
||||||
"membership_end": "273 00:00:00"
|
"membership_end": "273 00:00:00"
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Kfet",
|
"name": "Kfet",
|
||||||
"email": "tresorerie.bde@example.com",
|
"email": "tresorerie.bde@example.com",
|
||||||
"membership_fee": 35,
|
"membership_fee": 3500,
|
||||||
"membership_duration": "396 00:00:00",
|
"membership_duration": "396 00:00:00",
|
||||||
"membership_start": "213 00:00:00",
|
"membership_start": "213 00:00:00",
|
||||||
"membership_end": "273 00:00:00"
|
"membership_end": "273 00:00:00"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -67,6 +68,13 @@ class Club(models.Model):
|
|||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
verbose_name=_('email'),
|
verbose_name=_('email'),
|
||||||
)
|
)
|
||||||
|
parent_club = models.ForeignKey(
|
||||||
|
'self',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_('parent club'),
|
||||||
|
)
|
||||||
|
|
||||||
# Memberships
|
# Memberships
|
||||||
membership_fee = models.PositiveIntegerField(
|
membership_fee = models.PositiveIntegerField(
|
||||||
@ -158,6 +166,12 @@ class Membership(models.Model):
|
|||||||
else:
|
else:
|
||||||
return self.date_start.toordinal() <= datetime.datetime.now().toordinal()
|
return self.date_start.toordinal() <= datetime.datetime.now().toordinal()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.club.parent_club is not None:
|
||||||
|
if not Membership.objects.filter(user=self.user, club=self.club.parent_club):
|
||||||
|
raise ValidationError(_('User is not a member of the parent club'))
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('membership')
|
verbose_name = _('membership')
|
||||||
verbose_name_plural = _('memberships')
|
verbose_name_plural = _('memberships')
|
||||||
|
@ -17,6 +17,7 @@ class ClubTable(tables.Table):
|
|||||||
fields = ('id', 'name', 'email')
|
fields = ('id', 'name', 'email')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
|
'id': lambda record: "row-" + str(record.pk),
|
||||||
'data-href': lambda record: record.pk
|
'data-href': lambda record: record.pk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,12 +12,14 @@ urlpatterns = [
|
|||||||
path('club/<int:pk>/', views.ClubDetailView.as_view(), name="club_detail"),
|
path('club/<int:pk>/', views.ClubDetailView.as_view(), name="club_detail"),
|
||||||
path('club/<int:pk>/add_member/', views.ClubAddMemberView.as_view(), name="club_add_member"),
|
path('club/<int:pk>/add_member/', views.ClubAddMemberView.as_view(), name="club_add_member"),
|
||||||
path('club/create/', views.ClubCreateView.as_view(), name="club_create"),
|
path('club/create/', views.ClubCreateView.as_view(), name="club_create"),
|
||||||
|
path('club/<int:pk>/update', views.ClubUpdateView.as_view(), name="club_update"),
|
||||||
|
path('club/<int:pk>/update_pic', views.ClubPictureUpdateView.as_view(), name="club_update_pic"),
|
||||||
|
path('club/<int:pk>/aliases', views.ClubAliasView.as_view(), name="club_alias"),
|
||||||
path('user/', views.UserListView.as_view(), name="user_list"),
|
path('user/', views.UserListView.as_view(), name="user_list"),
|
||||||
path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
|
path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
|
||||||
path('user/<int:pk>/update', views.UserUpdateView.as_view(), name="user_update_profile"),
|
path('user/<int:pk>/update', views.UserUpdateView.as_view(), name="user_update_profile"),
|
||||||
path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
|
path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
|
||||||
path('user/<int:pk>/aliases', views.AliasView.as_view(), name="user_alias"),
|
path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"),
|
||||||
path('user/aliases/delete/<int:pk>', views.DeleteAliasView.as_view(), name="user_alias_delete"),
|
|
||||||
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
||||||
# API for the user autocompleter
|
# API for the user autocompleter
|
||||||
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
|
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
|
||||||
|
@ -20,7 +20,8 @@ from django.views.generic import CreateView, DetailView, UpdateView, TemplateVie
|
|||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from note.forms import AliasForm, ImageForm
|
from note.forms import ImageForm
|
||||||
|
#from note.forms import AliasForm, ImageForm
|
||||||
from note.models import Alias, NoteUser
|
from note.models import Alias, NoteUser
|
||||||
from note.models.transactions import Transaction
|
from note.models.transactions import Transaction
|
||||||
from note.tables import HistoryTable, AliasTable
|
from note.tables import HistoryTable, AliasTable
|
||||||
@ -143,10 +144,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
|
|||||||
club_list = \
|
club_list = \
|
||||||
Membership.objects.all().filter(user=user).only("club")
|
Membership.objects.all().filter(user=user).only("club")
|
||||||
context['club_list'] = ClubTable(club_list)
|
context['club_list'] = ClubTable(club_list)
|
||||||
context['title'] = _("Account #%(id)s: %(username)s") % {
|
|
||||||
'id': user.pk,
|
|
||||||
'username': user.username,
|
|
||||||
}
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -172,61 +169,19 @@ class UserListView(LoginRequiredMixin, SingleTableView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class AliasView(LoginRequiredMixin, FormMixin, DetailView):
|
class ProfileAliasView(LoginRequiredMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'member/profile_alias.html'
|
template_name = 'member/profile_alias.html'
|
||||||
context_object_name = 'user_object'
|
context_object_name = 'user_object'
|
||||||
form_class = AliasForm
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
note = context['user_object'].note
|
note = context['object'].note
|
||||||
context["aliases"] = AliasTable(note.alias_set.all())
|
context["aliases"] = AliasTable(note.alias_set.all())
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
class PictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
||||||
self.object = self.get_object()
|
|
||||||
form = self.get_form()
|
|
||||||
if form.is_valid():
|
|
||||||
return self.form_valid(form)
|
|
||||||
else:
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
alias = form.save(commit=False)
|
|
||||||
alias.note = self.object.note
|
|
||||||
alias.save()
|
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteAliasView(LoginRequiredMixin, DeleteView):
|
|
||||||
model = Alias
|
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
self.object = self.get_object()
|
|
||||||
self.object.delete()
|
|
||||||
except ValidationError as e:
|
|
||||||
# TODO: pass message to redirected view.
|
|
||||||
messages.error(self.request, str(e))
|
|
||||||
else:
|
|
||||||
messages.success(self.request, _("Alias successfully deleted"))
|
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.note.user.pk})
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
|
||||||
model = User
|
|
||||||
template_name = 'member/profile_picture_update.html'
|
|
||||||
context_object_name = 'user_object'
|
|
||||||
form_class = ImageForm
|
form_class = ImageForm
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
@ -273,6 +228,12 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
|
|||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class ProfilePictureUpdateView(PictureUpdateView):
|
||||||
|
model = User
|
||||||
|
template_name = 'member/profile_picture_update.html'
|
||||||
|
context_object_name = 'user_object'
|
||||||
|
|
||||||
|
|
||||||
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
Affiche le jeton d'authentification, et permet de le regénérer
|
Affiche le jeton d'authentification, et permet de le regénérer
|
||||||
@ -329,6 +290,7 @@ class ClubCreateView(LoginRequiredMixin, CreateView):
|
|||||||
"""
|
"""
|
||||||
model = Club
|
model = Club
|
||||||
form_class = ClubForm
|
form_class = ClubForm
|
||||||
|
success_url = reverse_lazy('member:club_list')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
@ -364,6 +326,34 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
|
|||||||
context['member_list'] = club_member
|
context['member_list'] = club_member
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class ClubAliasView(LoginRequiredMixin, DetailView):
|
||||||
|
model = Club
|
||||||
|
template_name = 'member/club_alias.html'
|
||||||
|
context_object_name = 'club'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
note = context['object'].note
|
||||||
|
context["aliases"] = AliasTable(note.alias_set.all())
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ClubUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
model = Club
|
||||||
|
context_object_name = "club"
|
||||||
|
form_class = ClubForm
|
||||||
|
template_name = "member/club_form.html"
|
||||||
|
success_url = reverse_lazy("member:club_detail")
|
||||||
|
|
||||||
|
|
||||||
|
class ClubPictureUpdateView(PictureUpdateView):
|
||||||
|
model = Club
|
||||||
|
template_name = 'member/club_picture_update.html'
|
||||||
|
context_object_name = 'club'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.id})
|
||||||
|
|
||||||
|
|
||||||
class ClubAddMemberView(LoginRequiredMixin, CreateView):
|
class ClubAddMemberView(LoginRequiredMixin, CreateView):
|
||||||
model = Membership
|
model = Membership
|
||||||
@ -374,12 +364,12 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
|
|||||||
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
|
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
|
||||||
| PermissionBackend.filter_queryset(self.request.user, Membership,
|
| PermissionBackend.filter_queryset(self.request.user, Membership,
|
||||||
"change"))
|
"change"))
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
club = Club.objects.get(pk=self.kwargs["pk"])
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['formset'] = MemberFormSet()
|
context['formset'] = MemberFormSet()
|
||||||
context['helper'] = FormSetHelper()
|
context['helper'] = FormSetHelper()
|
||||||
|
context['club'] = club
|
||||||
context['no_cache'] = True
|
context['no_cache'] = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -78,7 +78,11 @@ class AliasSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ('note', )
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
instance = Alias(**attrs)
|
||||||
|
instance.clean()
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class NotePolymorphicSerializer(PolymorphicSerializer):
|
class NotePolymorphicSerializer(PolymorphicSerializer):
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||||
@ -52,6 +57,22 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
ordering_fields = ['name', 'normalized_name']
|
ordering_fields = ['name', 'normalized_name']
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
serializer_class = self.serializer_class
|
||||||
|
if self.request.method in ['PUT', 'PATCH']:
|
||||||
|
#alias owner cannot be change once establish
|
||||||
|
setattr(serializer_class.Meta, 'read_only_fields', ('note',))
|
||||||
|
return serializer_class
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
try:
|
||||||
|
self.perform_destroy(instance)
|
||||||
|
except ValidationError as e:
|
||||||
|
print(e)
|
||||||
|
return Response({e.code:e.message},status.HTTP_400_BAD_REQUEST)
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Parse query and apply filters.
|
Parse query and apply filters.
|
||||||
@ -104,7 +125,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
|
|||||||
search_fields = ['$name', ]
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateViewSet(ReadProtectedModelViewSet):
|
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
||||||
@ -112,8 +133,9 @@ class TransactionTemplateViewSet(ReadProtectedModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = TransactionTemplate.objects.all()
|
queryset = TransactionTemplate.objects.all()
|
||||||
serializer_class = TransactionTemplateSerializer
|
serializer_class = TransactionTemplateSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||||
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionViewSet(ReadProtectedModelViewSet):
|
class TransactionViewSet(ReadProtectedModelViewSet):
|
||||||
|
58
apps/note/fixtures/button.json
Normal file
58
apps/note/fixtures/button.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "Soft"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"name": "Pulls"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 3,
|
||||||
|
"fields": {
|
||||||
|
"name": "Gala"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 4,
|
||||||
|
"fields": {
|
||||||
|
"name": "Clubs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 5,
|
||||||
|
"fields": {
|
||||||
|
"name": "Bouffe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 6,
|
||||||
|
"fields": {
|
||||||
|
"name": "BDA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 7,
|
||||||
|
"fields": {
|
||||||
|
"name": "Autre"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "note.templatecategory",
|
||||||
|
"pk": 8,
|
||||||
|
"fields": {
|
||||||
|
"name": "Alcool"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -70,7 +70,7 @@
|
|||||||
"balance": 0,
|
"balance": 0,
|
||||||
"last_negative": null,
|
"last_negative": null,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
"display_image": "pic/default.png",
|
||||||
"created_at": "2020-02-20T20:09:38.615Z"
|
"created_at": "2020-02-20T20:09:38.615Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -85,23 +85,8 @@
|
|||||||
"balance": 0,
|
"balance": 0,
|
||||||
"last_negative": null,
|
"last_negative": null,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"display_image": "",
|
|
||||||
"created_at": "2020-02-20T20:16:14.753Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"noteuser"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "pic/default.png",
|
"display_image": "pic/default.png",
|
||||||
"created_at": "2020-03-22T13:01:35.680Z"
|
"created_at": "2020-02-20T20:16:14.753Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -199,61 +184,5 @@
|
|||||||
"normalized_name": "kfet",
|
"normalized_name": "kfet",
|
||||||
"note": 6
|
"note": 6
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"name": "Soft"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"name": "Pulls"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"name": "Gala"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"name": "Clubs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"name": "Bouffe"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"name": "BDA"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"name": "Autre"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.templatecategory",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"name": "Alcool"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -9,17 +9,6 @@ from .models import Alias
|
|||||||
from .models import TransactionTemplate
|
from .models import TransactionTemplate
|
||||||
|
|
||||||
|
|
||||||
class AliasForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Alias
|
|
||||||
fields = ("name",)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields["name"].label = False
|
|
||||||
self.fields["name"].widget.attrs = {"placeholder": _('New Alias')}
|
|
||||||
|
|
||||||
|
|
||||||
class ImageForm(forms.Form):
|
class ImageForm(forms.Form):
|
||||||
image = forms.ImageField(required=False,
|
image = forms.ImageField(required=False,
|
||||||
label=_('select an image'),
|
label=_('select an image'),
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||||
from .transactions import MembershipTransaction, Transaction, \
|
from .transactions import MembershipTransaction, Transaction, \
|
||||||
TemplateCategory, TransactionTemplate, RecurrentTransaction
|
TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Notes
|
# Notes
|
||||||
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
|
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
|
||||||
# Transactions
|
# Transactions
|
||||||
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
|
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
|
||||||
'RecurrentTransaction',
|
'RecurrentTransaction', 'SpecialTransaction',
|
||||||
]
|
]
|
||||||
|
@ -228,7 +228,7 @@ class Alias(models.Model):
|
|||||||
for cat in {'M', 'P', 'Z', 'C'})).casefold()
|
for cat in {'M', 'P', 'Z', 'C'})).casefold()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
normalized_name = Alias.normalize(self.name)
|
normalized_name = self.normalize(self.name)
|
||||||
if len(normalized_name) >= 255:
|
if len(normalized_name) >= 255:
|
||||||
raise ValidationError(_('Alias is too long.'),
|
raise ValidationError(_('Alias is too long.'),
|
||||||
code='alias_too_long')
|
code='alias_too_long')
|
||||||
@ -242,8 +242,12 @@ class Alias(models.Model):
|
|||||||
pass
|
pass
|
||||||
self.normalized_name = normalized_name
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
|
def save(self,*args,**kwargs):
|
||||||
|
self.normalized_name = self.normalize(self.name)
|
||||||
|
super().save(*args,**kwargs)
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
raise ValidationError(_("You can't delete your main alias."),
|
raise ValidationError(_("You can't delete your main alias."),
|
||||||
code="cant_delete_main_alias")
|
code="main_alias")
|
||||||
return super().delete(using, keep_parents)
|
return super().delete(using, keep_parents)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import F
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -93,12 +94,26 @@ class Transaction(PolymorphicModel):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
verbose_name=_('source'),
|
verbose_name=_('source'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
source_alias = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
default="", # Will be remplaced by the name of the note on save
|
||||||
|
verbose_name=_('used alias'),
|
||||||
|
)
|
||||||
|
|
||||||
destination = models.ForeignKey(
|
destination = models.ForeignKey(
|
||||||
Note,
|
Note,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='+',
|
related_name='+',
|
||||||
verbose_name=_('destination'),
|
verbose_name=_('destination'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
destination_alias = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
default="", # Will be remplaced by the name of the note on save
|
||||||
|
verbose_name=_('used alias'),
|
||||||
|
)
|
||||||
|
|
||||||
created_at = models.DateTimeField(
|
created_at = models.DateTimeField(
|
||||||
verbose_name=_('created at'),
|
verbose_name=_('created at'),
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
@ -115,11 +130,19 @@ class Transaction(PolymorphicModel):
|
|||||||
verbose_name=_('reason'),
|
verbose_name=_('reason'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
valid = models.BooleanField(
|
valid = models.BooleanField(
|
||||||
verbose_name=_('valid'),
|
verbose_name=_('valid'),
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
invalidity_reason = models.CharField(
|
||||||
|
verbose_name=_('invalidity reason'),
|
||||||
|
max_length=255,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("transaction")
|
verbose_name = _("transaction")
|
||||||
verbose_name_plural = _("transactions")
|
verbose_name_plural = _("transactions")
|
||||||
@ -134,6 +157,13 @@ class Transaction(PolymorphicModel):
|
|||||||
When saving, also transfer money between two notes
|
When saving, also transfer money between two notes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||||
|
if not self.source_alias:
|
||||||
|
self.source_alias = str(self.source)
|
||||||
|
|
||||||
|
if not self.destination_alias:
|
||||||
|
self.destination_alias = str(self.destination)
|
||||||
|
|
||||||
if self.source.pk == self.destination.pk:
|
if self.source.pk == self.destination.pk:
|
||||||
# When source == destination, no money is transfered
|
# When source == destination, no money is transfered
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
@ -152,6 +182,10 @@ class Transaction(PolymorphicModel):
|
|||||||
self.source.balance -= to_transfer
|
self.source.balance -= to_transfer
|
||||||
self.destination.balance += to_transfer
|
self.destination.balance += to_transfer
|
||||||
|
|
||||||
|
# When a transaction is declared valid, we ensure that the invalidity reason is null, if it was
|
||||||
|
# previously invalid
|
||||||
|
self.invalidity_reason = None
|
||||||
|
|
||||||
# We save first the transaction, in case of the user has no right to transfer money
|
# We save first the transaction, in case of the user has no right to transfer money
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -5,11 +5,12 @@ import html
|
|||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from django.utils.html import format_html
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models.notes import Alias
|
from .models.notes import Alias
|
||||||
from .models.transactions import Transaction
|
from .models.transactions import Transaction, TransactionTemplate
|
||||||
from .templatetags.pretty_money import pretty_money
|
from .templatetags.pretty_money import pretty_money
|
||||||
|
|
||||||
|
|
||||||
@ -20,19 +21,48 @@ class HistoryTable(tables.Table):
|
|||||||
'table table-condensed table-striped table-hover'
|
'table table-condensed table-striped table-hover'
|
||||||
}
|
}
|
||||||
model = Transaction
|
model = Transaction
|
||||||
exclude = ("id", "polymorphic_ctype", )
|
exclude = ("id", "polymorphic_ctype", "invalidity_reason", "source_alias", "destination_alias",)
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
sequence = ('...', 'type', 'total', 'valid',)
|
sequence = ('...', 'type', 'total', 'valid',)
|
||||||
orderable = False
|
orderable = False
|
||||||
|
|
||||||
|
source = tables.Column(
|
||||||
|
attrs={
|
||||||
|
"td": {
|
||||||
|
"data-toggle": "tooltip",
|
||||||
|
"title": lambda record: _("used alias").capitalize() + " : " + record.source_alias,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
destination = tables.Column(
|
||||||
|
attrs={
|
||||||
|
"td": {
|
||||||
|
"data-toggle": "tooltip",
|
||||||
|
"title": lambda record: _("used alias").capitalize() + " : " + record.destination_alias,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type = tables.Column()
|
type = tables.Column()
|
||||||
|
|
||||||
total = tables.Column() # will use Transaction.total() !!
|
total = tables.Column() # will use Transaction.total() !!
|
||||||
|
|
||||||
valid = tables.Column(attrs={"td": {"id": lambda record: "validate_" + str(record.id),
|
valid = tables.Column(
|
||||||
|
attrs={
|
||||||
|
"td": {
|
||||||
|
"id": lambda record: "validate_" + str(record.id),
|
||||||
"class": lambda record: str(record.valid).lower() + ' validate',
|
"class": lambda record: str(record.valid).lower() + ' validate',
|
||||||
"onclick": lambda record: 'de_validate(' + str(record.id) + ', '
|
"data-toggle": "tooltip",
|
||||||
+ str(record.valid).lower() + ')'}})
|
"title": lambda record: _("Click to invalidate") if record.valid else _("Click to validate"),
|
||||||
|
"onclick": lambda record: 'in_validate(' + str(record.id) + ', ' + str(record.valid).lower() + ')',
|
||||||
|
"onmouseover": lambda record: '$("#invalidity_reason_'
|
||||||
|
+ str(record.id) + '").show();$("#invalidity_reason_'
|
||||||
|
+ str(record.id) + '").focus();',
|
||||||
|
"onmouseout": lambda record: '$("#invalidity_reason_' + str(record.id) + '").hide()',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def order_total(self, queryset, is_descending):
|
def order_total(self, queryset, is_descending):
|
||||||
# needed for rendering
|
# needed for rendering
|
||||||
@ -53,15 +83,32 @@ class HistoryTable(tables.Table):
|
|||||||
def render_reason(self, value):
|
def render_reason(self, value):
|
||||||
return html.unescape(value)
|
return html.unescape(value)
|
||||||
|
|
||||||
def render_valid(self, value):
|
def render_valid(self, value, record):
|
||||||
return "✔" if value else "✖"
|
"""
|
||||||
|
When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason
|
||||||
|
"""
|
||||||
|
val = "✔" if value else "✖"
|
||||||
|
val += "<input type='text' class='form-control' id='invalidity_reason_" + str(record.id) \
|
||||||
|
+ "' value='" + (html.escape(record.invalidity_reason)
|
||||||
|
if record.invalidity_reason else ("" if value else str(_("No reason specified")))) \
|
||||||
|
+ "'" + ("" if value else " disabled") \
|
||||||
|
+ " placeholder='" + html.escape(_("invalidity reason").capitalize()) + "'" \
|
||||||
|
+ " style='position: absolute; width: 15em; margin-left: -15.5em; margin-top: -2em; display: none;'>"
|
||||||
|
return format_html(val)
|
||||||
|
|
||||||
|
|
||||||
|
# function delete_button(id) provided in template file
|
||||||
|
DELETE_TEMPLATE = """
|
||||||
|
<button id="{{ record.pk }}" class="btn btn-danger btn-sm" onclick="delete_button(this.id)"> {{ delete_trans }}</button>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AliasTable(tables.Table):
|
class AliasTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'class':
|
'class':
|
||||||
'table table condensed table-striped table-hover'
|
'table table condensed table-striped table-hover',
|
||||||
|
'id':"alias_table"
|
||||||
}
|
}
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = ('name',)
|
fields = ('name',)
|
||||||
@ -69,9 +116,37 @@ class AliasTable(tables.Table):
|
|||||||
|
|
||||||
show_header = False
|
show_header = False
|
||||||
name = tables.Column(attrs={'td': {'class': 'text-center'}})
|
name = tables.Column(attrs={'td': {'class': 'text-center'}})
|
||||||
delete = tables.LinkColumn('member:user_alias_delete',
|
|
||||||
args=[A('pk')],
|
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||||
|
extra_context={"delete_trans": _('delete')},
|
||||||
|
attrs={'td': {'class': 'col-sm-1'}})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonTable(tables.Table):
|
||||||
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'td': {'class': 'col-sm-2'},
|
'class':
|
||||||
'a': {'class': 'btn btn-danger'}},
|
'table table-bordered condensed table-hover'
|
||||||
text='delete', accessor='pk')
|
}
|
||||||
|
row_attrs = {
|
||||||
|
'class': lambda record: 'table-row ' + 'table-success' if record.display else 'table-danger',
|
||||||
|
'id': lambda record: "row-" + str(record.pk),
|
||||||
|
'data-href': lambda record: record.pk
|
||||||
|
}
|
||||||
|
|
||||||
|
model = TransactionTemplate
|
||||||
|
|
||||||
|
edit = tables.LinkColumn('note:template_update',
|
||||||
|
args=[A('pk')],
|
||||||
|
attrs={'td': {'class': 'col-sm-1'},
|
||||||
|
'a': {'class': 'btn btn-sm btn-primary'}},
|
||||||
|
text=_('edit'),
|
||||||
|
accessor='pk')
|
||||||
|
|
||||||
|
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||||
|
extra_context={"delete_trans": _('delete')},
|
||||||
|
attrs={'td': {'class': 'col-sm-1'}})
|
||||||
|
|
||||||
|
def render_amount(self, value):
|
||||||
|
return pretty_money(value)
|
||||||
|
@ -18,5 +18,10 @@ def pretty_money(value):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cents_to_euros(value):
|
||||||
|
return "{:.02f}".format(value / 100) if value else ""
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
register.filter('pretty_money', pretty_money)
|
register.filter('pretty_money', pretty_money)
|
||||||
|
register.filter('cents_to_euros', cents_to_euros)
|
||||||
|
@ -8,7 +8,7 @@ from .models import Note
|
|||||||
|
|
||||||
app_name = 'note'
|
app_name = 'note'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('transfer/', views.TransactionCreate.as_view(), name='transfer'),
|
path('transfer/', views.TransactionCreateView.as_view(), name='transfer'),
|
||||||
path('buttons/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'),
|
path('buttons/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'),
|
||||||
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
||||||
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
||||||
|
@ -6,22 +6,25 @@ 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 Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, ListView, UpdateView
|
from django.views.generic import CreateView, UpdateView
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .forms import TransactionTemplateForm
|
from .forms import TransactionTemplateForm
|
||||||
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial
|
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial
|
||||||
from .models.transactions import SpecialTransaction
|
from .models.transactions import SpecialTransaction
|
||||||
from .tables import HistoryTable
|
from .tables import HistoryTable, ButtonTable
|
||||||
|
|
||||||
|
|
||||||
class TransactionCreate(LoginRequiredMixin, SingleTableView):
|
class TransactionCreateView(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
Show transfer page
|
View for the creation of Transaction between two note which are not :models:`transactions.RecurrentTransaction`.
|
||||||
|
e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial`
|
||||||
"""
|
"""
|
||||||
template_name = "note/transaction_form.html"
|
template_name = "note/transaction_form.html"
|
||||||
|
|
||||||
|
model = Transaction
|
||||||
# Transaction history table
|
# Transaction history table
|
||||||
table_class = HistoryTable
|
table_class = HistoryTable
|
||||||
table_pagination = {"per_page": 50}
|
table_pagination = {"per_page": 50}
|
||||||
@ -46,13 +49,14 @@ class TransactionCreate(LoginRequiredMixin, SingleTableView):
|
|||||||
|
|
||||||
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
"""
|
"""
|
||||||
Auto complete note by aliases
|
Auto complete note by aliases. Used in every search field for note
|
||||||
|
ex: :view:`ConsoView`, :view:`TransactionCreateView`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
When someone look for an :models:`note.Alias`, a query is sent to the dedicated API.
|
||||||
Cette fonction récupère la requête, et renvoie la liste filtrée des aliases.
|
This function handles the result and return a filtered list of aliases.
|
||||||
"""
|
"""
|
||||||
# Un utilisateur non connecté n'a accès à aucune information
|
# Un utilisateur non connecté n'a accès à aucune information
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
@ -81,6 +85,10 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_result_label(self, result):
|
def get_result_label(self, result):
|
||||||
|
"""
|
||||||
|
Show the selected alias and the username associated
|
||||||
|
<Alias> (aka. <Username> )
|
||||||
|
"""
|
||||||
# Gère l'affichage de l'alias dans la recherche
|
# Gère l'affichage de l'alias dans la recherche
|
||||||
res = result.name
|
res = result.name
|
||||||
note_name = str(result.note)
|
note_name = str(result.note)
|
||||||
@ -89,7 +97,9 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def get_result_value(self, result):
|
def get_result_value(self, result):
|
||||||
# Le résultat renvoyé doit être l'identifiant de la note, et non de l'alias
|
"""
|
||||||
|
The value used for the transactions will be the id of the Note.
|
||||||
|
"""
|
||||||
return str(result.note.pk)
|
return str(result.note.pk)
|
||||||
|
|
||||||
|
|
||||||
@ -99,14 +109,15 @@ class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
|
|||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
form_class = TransactionTemplateForm
|
||||||
|
success_url = reverse_lazy('note:template_list')
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateListView(LoginRequiredMixin, ListView):
|
class TransactionTemplateListView(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
List TransactionsTemplates
|
List TransactionsTemplates
|
||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
table_class = ButtonTable
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
@ -114,11 +125,13 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
form_class = TransactionTemplateForm
|
||||||
|
success_url = reverse_lazy('note:template_list')
|
||||||
|
|
||||||
|
|
||||||
class ConsoView(LoginRequiredMixin, SingleTableView):
|
class ConsoView(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
Consume
|
The Magic View that make people pay their beer and burgers.
|
||||||
|
(Most of the magic happens in the dark world of Javascript see consos.js)
|
||||||
"""
|
"""
|
||||||
template_name = "note/conso_form.html"
|
template_name = "note/conso_form.html"
|
||||||
|
|
||||||
|
@ -28,4 +28,3 @@ class RolePermissionsAdmin(admin.ModelAdmin):
|
|||||||
Admin customisation for RolePermissions
|
Admin customisation for RolePermissions
|
||||||
"""
|
"""
|
||||||
list_display = ('role', )
|
list_display = ('role', )
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
|
||||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||||
|
|
||||||
from .serializers import PermissionSerializer
|
from .serializers import PermissionSerializer
|
||||||
from ..models import Permission
|
from ..models import Permission
|
||||||
|
|
||||||
|
@ -327,7 +327,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"transaction"
|
"transaction"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, {\"amount__lte\": [\"user\", \"note\", \"balance\"]}]",
|
"query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, [\"OR\", {\"amount__lte\": [\"user\", \"note\", \"balance\"]}, {\"valid\": false}]]",
|
||||||
"type": "add",
|
"type": "add",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
@ -387,7 +387,7 @@
|
|||||||
"note",
|
"note",
|
||||||
"recurrenttransaction"
|
"recurrenttransaction"
|
||||||
],
|
],
|
||||||
"query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]",
|
"query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}, {\"valid\": false}]]",
|
||||||
"type": "add",
|
"type": "add",
|
||||||
"mask": 2,
|
"mask": 2,
|
||||||
"field": "",
|
"field": "",
|
||||||
|
@ -10,7 +10,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q, Model
|
from django.db.models import F, Q, Model
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from member.models import Role
|
from member.models import Role
|
||||||
|
|
||||||
|
|
||||||
@ -281,4 +280,3 @@ class RolePermissions(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.role)
|
return str(self.role)
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
from rest_framework.permissions import DjangoObjectPermissions
|
from rest_framework.permissions import DjangoObjectPermissions
|
||||||
|
|
||||||
|
from .backends import PermissionBackend
|
||||||
|
|
||||||
SAFE_METHODS = ('HEAD', 'OPTIONS', )
|
SAFE_METHODS = ('HEAD', 'OPTIONS', )
|
||||||
|
|
||||||
|
|
||||||
@ -41,8 +43,8 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
|||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
perms = self.get_required_object_permissions(request.method, model_cls)
|
perms = self.get_required_object_permissions(request.method, model_cls)
|
||||||
|
# if not user.has_perms(perms, obj):
|
||||||
if not user.has_perms(perms, obj):
|
if not all(PermissionBackend().has_perm(user, perm, obj) for perm in perms):
|
||||||
# If the user does not have permissions we need to determine if
|
# If the user does not have permissions we need to determine if
|
||||||
# they have read permissions to see 403, or not, and simply see
|
# they have read permissions to see 403, or not, and simply see
|
||||||
# a 404 response.
|
# a 404 response.
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models.signals import pre_save, pre_delete, post_save, post_delete
|
from django.db.models.signals import pre_save, pre_delete, post_save, post_delete
|
||||||
|
|
||||||
from logs import signals as logs_signals
|
from logs import signals as logs_signals
|
||||||
from permission.backends import PermissionBackend
|
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_authenticated_user
|
||||||
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
EXCLUDED = [
|
EXCLUDED = [
|
||||||
|
@ -3,10 +3,8 @@
|
|||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
|
|
||||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
|
4
apps/treasury/__init__.py
Normal file
4
apps/treasury/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
default_app_config = 'treasury.apps.TreasuryConfig'
|
27
apps/treasury/admin.py
Normal file
27
apps/treasury/admin.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-lateré
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import RemittanceType, Remittance
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(RemittanceType)
|
||||||
|
class RemittanceTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin customisation for RemiitanceType
|
||||||
|
"""
|
||||||
|
list_display = ('note', )
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Remittance)
|
||||||
|
class RemittanceAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin customisation for Remittance
|
||||||
|
"""
|
||||||
|
list_display = ('remittance_type', 'date', 'comment', 'count', 'amount', 'closed', )
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if not obj:
|
||||||
|
return True
|
||||||
|
return not obj.closed and super().has_change_permission(request, obj)
|
0
apps/treasury/api/__init__.py
Normal file
0
apps/treasury/api/__init__.py
Normal file
62
apps/treasury/api/serializers.py
Normal file
62
apps/treasury/api/serializers.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from note.api.serializers import SpecialTransactionSerializer
|
||||||
|
|
||||||
|
from ..models import Invoice, Product, RemittanceType, Remittance
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Product types.
|
||||||
|
The djangorestframework plugin will analyse the model `Product` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Invoice types.
|
||||||
|
The djangorestframework plugin will analyse the model `Invoice` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Invoice
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ('bde',)
|
||||||
|
|
||||||
|
products = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_products(self, obj):
|
||||||
|
return serializers.ListSerializer(child=ProductSerializer())\
|
||||||
|
.to_representation(Product.objects.filter(invoice=obj).all())
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceTypeSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for RemittanceType types.
|
||||||
|
The djangorestframework plugin will analyse the model `RemittanceType` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RemittanceType
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
REST API Serializer for Remittance types.
|
||||||
|
The djangorestframework plugin will analyse the model `Remittance` and parse all fields in the API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
transactions = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Remittance
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_transactions(self, obj):
|
||||||
|
return serializers.ListSerializer(child=SpecialTransactionSerializer()).to_representation(obj.transactions)
|
14
apps/treasury/api/urls.py
Normal file
14
apps/treasury/api/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet
|
||||||
|
|
||||||
|
|
||||||
|
def register_treasury_urls(router, path):
|
||||||
|
"""
|
||||||
|
Configure router for treasury REST API.
|
||||||
|
"""
|
||||||
|
router.register(path + '/invoice', InvoiceViewSet)
|
||||||
|
router.register(path + '/product', ProductViewSet)
|
||||||
|
router.register(path + '/remittance_type', RemittanceTypeViewSet)
|
||||||
|
router.register(path + '/remittance', RemittanceViewSet)
|
53
apps/treasury/api/views.py
Normal file
53
apps/treasury/api/views.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
|
|
||||||
|
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer
|
||||||
|
from ..models import Invoice, Product, RemittanceType, Remittance
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceViewSet(ReadProtectedModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
|
||||||
|
then render it on /api/treasury/invoice/
|
||||||
|
"""
|
||||||
|
queryset = Invoice.objects.all()
|
||||||
|
serializer_class = InvoiceSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['bde', ]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductViewSet(ReadProtectedModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,
|
||||||
|
then render it on /api/treasury/product/
|
||||||
|
"""
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
filter_backends = [SearchFilter]
|
||||||
|
search_fields = ['$designation', ]
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
|
||||||
|
then render it on /api/treasury/remittance_type/
|
||||||
|
"""
|
||||||
|
queryset = RemittanceType.objects.all()
|
||||||
|
serializer_class = RemittanceTypeSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceViewSet(ReadProtectedModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
|
||||||
|
then render it on /api/treasury/remittance/
|
||||||
|
"""
|
||||||
|
queryset = Remittance.objects.all()
|
||||||
|
serializer_class = RemittanceSerializer
|
33
apps/treasury/apps.py
Normal file
33
apps/treasury/apps.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.db.models.signals import post_save, post_migrate
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class TreasuryConfig(AppConfig):
|
||||||
|
name = 'treasury'
|
||||||
|
verbose_name = _('Treasury')
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
"""
|
||||||
|
Define app internal signals to interact with other apps
|
||||||
|
"""
|
||||||
|
|
||||||
|
from . import signals
|
||||||
|
from note.models import SpecialTransaction, NoteSpecial
|
||||||
|
from treasury.models import SpecialTransactionProxy
|
||||||
|
post_save.connect(signals.save_special_transaction, sender=SpecialTransaction)
|
||||||
|
|
||||||
|
def setup_specialtransactions_proxies(**kwargs):
|
||||||
|
# If the treasury app was disabled for any reason during a certain amount of time,
|
||||||
|
# we ensure that each special transaction is linked to a proxy
|
||||||
|
for transaction in SpecialTransaction.objects.filter(
|
||||||
|
source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||||
|
specialtransactionproxy=None,
|
||||||
|
):
|
||||||
|
SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None)
|
||||||
|
|
||||||
|
post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy)
|
9
apps/treasury/fixtures/initial.json
Normal file
9
apps/treasury/fixtures/initial.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "treasury.remittancetype",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"note": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
156
apps/treasury/forms.py
Normal file
156
apps/treasury/forms.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from crispy_forms.layout import Submit
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .models import Invoice, Product, Remittance, SpecialTransactionProxy
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Create and generate invoices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Django forms don't support date fields. We have to add it manually
|
||||||
|
date = forms.DateField(
|
||||||
|
initial=datetime.date.today,
|
||||||
|
widget=forms.TextInput(attrs={'type': 'date'})
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_date(self):
|
||||||
|
self.instance.date = self.data.get("date")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Invoice
|
||||||
|
exclude = ('bde', )
|
||||||
|
|
||||||
|
|
||||||
|
# Add a subform per product in the invoice form, and manage correctly the link between the invoice and
|
||||||
|
# its products. The FormSet will search automatically the ForeignKey in the Product model.
|
||||||
|
ProductFormSet = forms.inlineformset_factory(
|
||||||
|
Invoice,
|
||||||
|
Product,
|
||||||
|
fields='__all__',
|
||||||
|
extra=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFormSetHelper(FormHelper):
|
||||||
|
"""
|
||||||
|
Specify some template informations for the product form.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, form=None):
|
||||||
|
super().__init__(form)
|
||||||
|
self.form_tag = False
|
||||||
|
self.form_method = 'POST'
|
||||||
|
self.form_class = 'form-inline'
|
||||||
|
self.template = 'bootstrap4/table_inline_formset.html'
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Create remittances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
|
||||||
|
# We can't update the type of the remittance once created.
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields["remittance_type"].disabled = True
|
||||||
|
self.fields["remittance_type"].required = False
|
||||||
|
|
||||||
|
# We display the submit button iff the remittance is open,
|
||||||
|
# the close button iff it is open and has a linked transaction
|
||||||
|
if not self.instance.closed:
|
||||||
|
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
||||||
|
if self.instance.transactions:
|
||||||
|
self.helper.add_input(Submit("close", _("Close"), css_class='btn btn-success'))
|
||||||
|
else:
|
||||||
|
# If the remittance is closed, we can't change anything
|
||||||
|
self.fields["comment"].disabled = True
|
||||||
|
self.fields["comment"].required = False
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
# We can't update anything if the remittance is already closed.
|
||||||
|
if self.instance.closed:
|
||||||
|
self.add_error("comment", _("Remittance is already closed."))
|
||||||
|
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
|
if self.instance.pk and cleaned_data.get("remittance_type") != self.instance.remittance_type:
|
||||||
|
self.add_error("remittance_type", _("You can't change the type of the remittance."))
|
||||||
|
|
||||||
|
# The close button is manually handled
|
||||||
|
if "close" in self.data:
|
||||||
|
self.instance.closed = True
|
||||||
|
self.cleaned_data["closed"] = True
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Remittance
|
||||||
|
fields = ('remittance_type', 'comment',)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Attach a special transaction to a remittance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Since we use a proxy model for special transactions, we add manually the fields related to the transaction
|
||||||
|
last_name = forms.CharField(label=_("Last name"))
|
||||||
|
|
||||||
|
first_name = forms.Field(label=_("First name"))
|
||||||
|
|
||||||
|
bank = forms.Field(label=_("Bank"))
|
||||||
|
|
||||||
|
amount = forms.IntegerField(label=_("Amount"), min_value=0)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.helper = FormHelper()
|
||||||
|
# Add submit button
|
||||||
|
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
||||||
|
|
||||||
|
self.fields["remittance"].queryset = Remittance.objects.filter(closed=False)
|
||||||
|
|
||||||
|
def clean_last_name(self):
|
||||||
|
"""
|
||||||
|
Replace the first name in the information of the transaction.
|
||||||
|
"""
|
||||||
|
self.instance.transaction.last_name = self.data.get("last_name")
|
||||||
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
|
def clean_first_name(self):
|
||||||
|
"""
|
||||||
|
Replace the last name in the information of the transaction.
|
||||||
|
"""
|
||||||
|
self.instance.transaction.first_name = self.data.get("first_name")
|
||||||
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
|
def clean_bank(self):
|
||||||
|
"""
|
||||||
|
Replace the bank in the information of the transaction.
|
||||||
|
"""
|
||||||
|
self.instance.transaction.bank = self.data.get("bank")
|
||||||
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
|
def clean_amount(self):
|
||||||
|
"""
|
||||||
|
Replace the amount of the transaction.
|
||||||
|
"""
|
||||||
|
self.instance.transaction.amount = self.data.get("amount")
|
||||||
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SpecialTransactionProxy
|
||||||
|
fields = ('remittance', )
|
0
apps/treasury/migrations/__init__.py
Normal file
0
apps/treasury/migrations/__init__.py
Normal file
189
apps/treasury/models.py
Normal file
189
apps/treasury/models.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from note.models import NoteSpecial, SpecialTransaction
|
||||||
|
|
||||||
|
|
||||||
|
class Invoice(models.Model):
|
||||||
|
"""
|
||||||
|
An invoice model that can generates a true invoice.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = models.PositiveIntegerField(
|
||||||
|
primary_key=True,
|
||||||
|
verbose_name=_("Invoice identifier"),
|
||||||
|
)
|
||||||
|
|
||||||
|
bde = models.CharField(
|
||||||
|
max_length=32,
|
||||||
|
default='Saperlistpopette.png',
|
||||||
|
choices=(
|
||||||
|
('Saperlistpopette.png', 'Saper[list]popette'),
|
||||||
|
('Finalist.png', 'Fina[list]'),
|
||||||
|
('Listorique.png', '[List]orique'),
|
||||||
|
('Satellist.png', 'Satel[list]'),
|
||||||
|
('Monopolist.png', 'Monopo[list]'),
|
||||||
|
('Kataclist.png', 'Katac[list]'),
|
||||||
|
),
|
||||||
|
verbose_name=_("BDE"),
|
||||||
|
)
|
||||||
|
|
||||||
|
object = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Object"),
|
||||||
|
)
|
||||||
|
|
||||||
|
description = models.TextField(
|
||||||
|
verbose_name=_("Description")
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
address = models.TextField(
|
||||||
|
verbose_name=_("Address"),
|
||||||
|
)
|
||||||
|
|
||||||
|
date = models.DateField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name=_("Place"),
|
||||||
|
)
|
||||||
|
|
||||||
|
acquitted = models.BooleanField(
|
||||||
|
verbose_name=_("Acquitted"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Product(models.Model):
|
||||||
|
"""
|
||||||
|
Product that appears on an invoice.
|
||||||
|
"""
|
||||||
|
|
||||||
|
invoice = models.ForeignKey(
|
||||||
|
Invoice,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
designation = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Designation"),
|
||||||
|
)
|
||||||
|
|
||||||
|
quantity = models.PositiveIntegerField(
|
||||||
|
verbose_name=_("Quantity")
|
||||||
|
)
|
||||||
|
|
||||||
|
amount = models.IntegerField(
|
||||||
|
verbose_name=_("Unit price")
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_euros(self):
|
||||||
|
return self.amount / 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total(self):
|
||||||
|
return self.quantity * self.amount
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_euros(self):
|
||||||
|
return self.total / 100
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceType(models.Model):
|
||||||
|
"""
|
||||||
|
Store what kind of remittances can be stored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
note = models.OneToOneField(
|
||||||
|
NoteSpecial,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.note)
|
||||||
|
|
||||||
|
|
||||||
|
class Remittance(models.Model):
|
||||||
|
"""
|
||||||
|
Treasurers want to regroup checks or bank transfers in bank remittances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
date = models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name=_("Date"),
|
||||||
|
)
|
||||||
|
|
||||||
|
remittance_type = models.ForeignKey(
|
||||||
|
RemittanceType,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Type"),
|
||||||
|
)
|
||||||
|
|
||||||
|
comment = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Comment"),
|
||||||
|
)
|
||||||
|
|
||||||
|
closed = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Closed"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transactions(self):
|
||||||
|
"""
|
||||||
|
:return: Transactions linked to this remittance.
|
||||||
|
"""
|
||||||
|
if not self.pk:
|
||||||
|
return SpecialTransaction.objects.none()
|
||||||
|
return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self)
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
"""
|
||||||
|
Linked transactions count.
|
||||||
|
"""
|
||||||
|
return self.transactions.count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount(self):
|
||||||
|
"""
|
||||||
|
Total amount of the remittance.
|
||||||
|
"""
|
||||||
|
return sum(transaction.total for transaction in self.transactions.all())
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
|
# Check if all transactions have the right type.
|
||||||
|
if self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||||
|
raise ValidationError("All transactions in a remittance must have the same type")
|
||||||
|
|
||||||
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Remittance #{:d}: {}").format(self.id, self.comment, )
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialTransactionProxy(models.Model):
|
||||||
|
"""
|
||||||
|
In order to keep modularity, we don't that the Note app depends on the treasury app.
|
||||||
|
That's why we create a proxy in this app, to link special transactions and remittances.
|
||||||
|
If it isn't very clean, that makes what we want.
|
||||||
|
"""
|
||||||
|
|
||||||
|
transaction = models.OneToOneField(
|
||||||
|
SpecialTransaction,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
|
||||||
|
remittance = models.ForeignKey(
|
||||||
|
Remittance,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Remittance"),
|
||||||
|
)
|
12
apps/treasury/signals.py
Normal file
12
apps/treasury/signals.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from treasury.models import SpecialTransactionProxy, RemittanceType
|
||||||
|
|
||||||
|
|
||||||
|
def save_special_transaction(instance, created, **kwargs):
|
||||||
|
"""
|
||||||
|
When a special transaction is created, we create its linked proxy
|
||||||
|
"""
|
||||||
|
if created and RemittanceType.objects.filter(note=instance.source).exists():
|
||||||
|
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
|
103
apps/treasury/tables.py
Normal file
103
apps/treasury/tables.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_tables2 import A
|
||||||
|
from note.models import SpecialTransaction
|
||||||
|
from note.templatetags.pretty_money import pretty_money
|
||||||
|
|
||||||
|
from .models import Invoice, Remittance
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List all invoices.
|
||||||
|
"""
|
||||||
|
id = tables.LinkColumn("treasury:invoice_update",
|
||||||
|
args=[A("pk")],
|
||||||
|
text=lambda record: _("Invoice #{:d}").format(record.id), )
|
||||||
|
|
||||||
|
invoice = tables.LinkColumn("treasury:invoice_render",
|
||||||
|
verbose_name=_("Invoice"),
|
||||||
|
args=[A("pk")],
|
||||||
|
accessor="pk",
|
||||||
|
text="",
|
||||||
|
attrs={
|
||||||
|
'a': {'class': 'fa fa-file-pdf-o'},
|
||||||
|
'td': {'data-turbolinks': 'false'}
|
||||||
|
})
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped table-hover'
|
||||||
|
}
|
||||||
|
model = Invoice
|
||||||
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
fields = ('id', 'name', 'object', 'acquitted', 'invoice',)
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List all remittances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
count = tables.Column(verbose_name=_("Transaction count"))
|
||||||
|
|
||||||
|
amount = tables.Column(verbose_name=_("Amount"))
|
||||||
|
|
||||||
|
view = tables.LinkColumn("treasury:remittance_update",
|
||||||
|
verbose_name=_("View"),
|
||||||
|
args=[A("pk")],
|
||||||
|
text=_("View"),
|
||||||
|
attrs={
|
||||||
|
'a': {'class': 'btn btn-primary'}
|
||||||
|
}, )
|
||||||
|
|
||||||
|
def render_amount(self, value):
|
||||||
|
return pretty_money(value)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped table-hover'
|
||||||
|
}
|
||||||
|
model = Remittance
|
||||||
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
fields = ('id', 'date', 'remittance_type', 'comment', 'count', 'amount', 'view',)
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialTransactionTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List special credit transactions that are (or not, following the queryset) attached to a remittance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Display add and remove buttons. Use the `exclude` field to select what is needed.
|
||||||
|
remittance_add = tables.LinkColumn("treasury:link_transaction",
|
||||||
|
verbose_name=_("Remittance"),
|
||||||
|
args=[A("specialtransactionproxy.pk")],
|
||||||
|
text=_("Add"),
|
||||||
|
attrs={
|
||||||
|
'a': {'class': 'btn btn-primary'}
|
||||||
|
}, )
|
||||||
|
|
||||||
|
remittance_remove = tables.LinkColumn("treasury:unlink_transaction",
|
||||||
|
verbose_name=_("Remittance"),
|
||||||
|
args=[A("specialtransactionproxy.pk")],
|
||||||
|
text=_("Remove"),
|
||||||
|
attrs={
|
||||||
|
'a': {'class': 'btn btn-primary btn-danger'}
|
||||||
|
}, )
|
||||||
|
|
||||||
|
def render_id(self, record):
|
||||||
|
return record.specialtransactionproxy.pk
|
||||||
|
|
||||||
|
def render_amount(self, value):
|
||||||
|
return pretty_money(value)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped table-hover'
|
||||||
|
}
|
||||||
|
model = SpecialTransaction
|
||||||
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
fields = ('id', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',)
|
24
apps/treasury/urls.py
Normal file
24
apps/treasury/urls.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView, RemittanceListView,\
|
||||||
|
RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, UnlinkTransactionToRemittanceView
|
||||||
|
|
||||||
|
app_name = 'treasury'
|
||||||
|
urlpatterns = [
|
||||||
|
# Invoice app paths
|
||||||
|
path('invoice/', InvoiceListView.as_view(), name='invoice_list'),
|
||||||
|
path('invoice/create/', InvoiceCreateView.as_view(), name='invoice_create'),
|
||||||
|
path('invoice/<int:pk>/', InvoiceUpdateView.as_view(), name='invoice_update'),
|
||||||
|
path('invoice/render/<int:pk>/', InvoiceRenderView.as_view(), name='invoice_render'),
|
||||||
|
|
||||||
|
# Remittance app paths
|
||||||
|
path('remittance/', RemittanceListView.as_view(), name='remittance_list'),
|
||||||
|
path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'),
|
||||||
|
path('remittance/<int:pk>/', RemittanceUpdateView.as_view(), name='remittance_update'),
|
||||||
|
path('remittance/link_transaction/<int:pk>/', LinkTransactionToRemittanceView.as_view(), name='link_transaction'),
|
||||||
|
path('remittance/unlink_transaction/<int:pk>/', UnlinkTransactionToRemittanceView.as_view(),
|
||||||
|
name='unlink_transaction'),
|
||||||
|
]
|
316
apps/treasury/views.py
Normal file
316
apps/treasury/views.py
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.views.generic import CreateView, UpdateView
|
||||||
|
from django.views.generic.base import View, TemplateView
|
||||||
|
from django_tables2 import SingleTableView
|
||||||
|
from note.models import SpecialTransaction, NoteSpecial
|
||||||
|
from note_kfet.settings.base import BASE_DIR
|
||||||
|
|
||||||
|
from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
|
||||||
|
from .models import Invoice, Product, Remittance, SpecialTransactionProxy
|
||||||
|
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceCreateView(LoginRequiredMixin, CreateView):
|
||||||
|
"""
|
||||||
|
Create Invoice
|
||||||
|
"""
|
||||||
|
model = Invoice
|
||||||
|
form_class = InvoiceForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
form = context['form']
|
||||||
|
form.helper = FormHelper()
|
||||||
|
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||||
|
form.helper.form_tag = False
|
||||||
|
# The formset handles the set of the products
|
||||||
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
|
context['formset'] = form_set
|
||||||
|
context['helper'] = ProductFormSetHelper()
|
||||||
|
context['no_cache'] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
# The user type amounts in cents. We convert it in euros.
|
||||||
|
for key in self.request.POST:
|
||||||
|
value = self.request.POST[key]
|
||||||
|
if key.endswith("amount") and value:
|
||||||
|
kwargs[key] = str(int(100 * float(value)))
|
||||||
|
elif value:
|
||||||
|
kwargs[key] = value
|
||||||
|
|
||||||
|
# For each product, we save it
|
||||||
|
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||||
|
if formset.is_valid():
|
||||||
|
for f in formset:
|
||||||
|
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||||
|
if f.is_valid() and f.instance.designation:
|
||||||
|
f.save()
|
||||||
|
f.instance.save()
|
||||||
|
else:
|
||||||
|
f.instance = None
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:invoice_list')
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceListView(LoginRequiredMixin, SingleTableView):
|
||||||
|
"""
|
||||||
|
List existing Invoices
|
||||||
|
"""
|
||||||
|
model = Invoice
|
||||||
|
table_class = InvoiceTable
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
"""
|
||||||
|
Create Invoice
|
||||||
|
"""
|
||||||
|
model = Invoice
|
||||||
|
form_class = InvoiceForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
form = context['form']
|
||||||
|
form.helper = FormHelper()
|
||||||
|
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||||
|
form.helper.form_tag = False
|
||||||
|
# Fill the intial value for the date field, with the initial date of the model instance
|
||||||
|
form.fields['date'].initial = form.instance.date
|
||||||
|
# The formset handles the set of the products
|
||||||
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
|
context['formset'] = form_set
|
||||||
|
context['helper'] = ProductFormSetHelper()
|
||||||
|
context['no_cache'] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
# The user type amounts in cents. We convert it in euros.
|
||||||
|
for key in self.request.POST:
|
||||||
|
value = self.request.POST[key]
|
||||||
|
if key.endswith("amount") and value:
|
||||||
|
kwargs[key] = str(int(100 * float(value)))
|
||||||
|
elif value:
|
||||||
|
kwargs[key] = value
|
||||||
|
|
||||||
|
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||||
|
saved = []
|
||||||
|
# For each product, we save it
|
||||||
|
if formset.is_valid():
|
||||||
|
for f in formset:
|
||||||
|
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||||
|
if f.is_valid() and f.instance.designation:
|
||||||
|
f.save()
|
||||||
|
f.instance.save()
|
||||||
|
saved.append(f.instance.pk)
|
||||||
|
else:
|
||||||
|
f.instance = None
|
||||||
|
# Remove old products that weren't given in the form
|
||||||
|
Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:invoice_list')
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceRenderView(LoginRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Render Invoice as a generated PDF with the given information and a LaTeX template
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
pk = kwargs["pk"]
|
||||||
|
invoice = Invoice.objects.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.my_name = "BDE ENS Cachan"
|
||||||
|
invoice.my_address_street = "61 avenue du Président Wilson"
|
||||||
|
invoice.my_city = "94230 Cachan"
|
||||||
|
invoice.bank_code = 30003
|
||||||
|
invoice.desk_code = 3894
|
||||||
|
invoice.account_number = 37280662
|
||||||
|
invoice.rib_key = 14
|
||||||
|
invoice.bic = "SOGEFRPP"
|
||||||
|
|
||||||
|
# Replace line breaks with the LaTeX equivalent
|
||||||
|
invoice.description = invoice.description.replace("\r", "").replace("\n", "\\\\ ")
|
||||||
|
invoice.address = invoice.address.replace("\r", "").replace("\n", "\\\\ ")
|
||||||
|
# Fill the template with the information
|
||||||
|
tex = render_to_string("treasury/invoice_sample.tex", dict(obj=invoice, products=products))
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(BASE_DIR + "/tmp")
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
# We render the file in a temporary directory
|
||||||
|
tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open("{}/invoice-{:d}.tex".format(tmp_dir, pk), "wb") as f:
|
||||||
|
f.write(tex.encode("UTF-8"))
|
||||||
|
del tex
|
||||||
|
|
||||||
|
# The file has to be rendered twice
|
||||||
|
for _ in range(2):
|
||||||
|
error = subprocess.Popen(
|
||||||
|
["pdflatex", "invoice-{}.tex".format(pk)],
|
||||||
|
cwd=tmp_dir,
|
||||||
|
stdin=open(os.devnull, "r"),
|
||||||
|
stderr=open(os.devnull, "wb"),
|
||||||
|
stdout=open(os.devnull, "wb"),
|
||||||
|
).wait()
|
||||||
|
|
||||||
|
if error:
|
||||||
|
raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")")
|
||||||
|
|
||||||
|
# Display the generated pdf as a HTTP Response
|
||||||
|
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
|
||||||
|
response = HttpResponse(pdf, content_type="application/pdf")
|
||||||
|
response['Content-Disposition'] = "inline;filename=invoice-{:d}.pdf".format(pk)
|
||||||
|
except IOError as e:
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
# Delete all temporary files
|
||||||
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceCreateView(LoginRequiredMixin, CreateView):
|
||||||
|
"""
|
||||||
|
Create Remittance
|
||||||
|
"""
|
||||||
|
model = Remittance
|
||||||
|
form_class = RemittanceForm
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:remittance_list')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
ctx["table"] = RemittanceTable(data=Remittance.objects.all())
|
||||||
|
ctx["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none())
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceListView(LoginRequiredMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
List existing Remittances
|
||||||
|
"""
|
||||||
|
template_name = "treasury/remittance_list.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all())
|
||||||
|
ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all())
|
||||||
|
|
||||||
|
ctx["special_transactions_no_remittance"] = SpecialTransactionTable(
|
||||||
|
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||||
|
specialtransactionproxy__remittance=None).all(),
|
||||||
|
exclude=('remittance_remove', ))
|
||||||
|
ctx["special_transactions_with_remittance"] = SpecialTransactionTable(
|
||||||
|
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||||
|
specialtransactionproxy__remittance__closed=False).all(),
|
||||||
|
exclude=('remittance_add', ))
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
"""
|
||||||
|
Update Remittance
|
||||||
|
"""
|
||||||
|
model = Remittance
|
||||||
|
form_class = RemittanceForm
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:remittance_list')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
ctx["table"] = RemittanceTable(data=Remittance.objects.all())
|
||||||
|
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all()
|
||||||
|
ctx["special_transactions"] = SpecialTransactionTable(
|
||||||
|
data=data,
|
||||||
|
exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView):
|
||||||
|
"""
|
||||||
|
Attach a special transaction to a remittance
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = SpecialTransactionProxy
|
||||||
|
form_class = LinkTransactionToRemittanceForm
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:remittance_list')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
form = ctx["form"]
|
||||||
|
form.fields["last_name"].initial = self.object.transaction.last_name
|
||||||
|
form.fields["first_name"].initial = self.object.transaction.first_name
|
||||||
|
form.fields["bank"].initial = self.object.transaction.bank
|
||||||
|
form.fields["amount"].initial = self.object.transaction.amount
|
||||||
|
form.fields["remittance"].queryset = form.fields["remittance"] \
|
||||||
|
.queryset.filter(remittance_type__note=self.object.transaction.source)
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Unlink a special transaction and its remittance
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
pk = kwargs["pk"]
|
||||||
|
transaction = SpecialTransactionProxy.objects.get(pk=pk)
|
||||||
|
|
||||||
|
# The remittance must be open (or inexistant)
|
||||||
|
if transaction.remittance and transaction.remittance.closed:
|
||||||
|
raise ValidationError("Remittance is already closed.")
|
||||||
|
|
||||||
|
transaction.remittance = None
|
||||||
|
transaction.save()
|
||||||
|
|
||||||
|
return redirect('treasury:remittance_list')
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-03-16 11:53+0100\n"
|
"POT-Creation-Date: 2020-03-26 14:40+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -23,9 +23,9 @@ msgid "activity"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||||
#: apps/member/models.py:61 apps/member/models.py:112
|
#: apps/member/models.py:63 apps/member/models.py:114
|
||||||
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
|
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25
|
||||||
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:202
|
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
|
||||||
#: templates/member/profile_detail.html:15
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -46,12 +46,13 @@ msgstr ""
|
|||||||
msgid "activity types"
|
msgid "activity types"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/models.py:48 apps/note/models/transactions.py:69
|
#: apps/activity/models.py:48 apps/note/models/transactions.py:70
|
||||||
|
#: apps/permission/models.py:91
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||||
#: apps/note/models/transactions.py:62 apps/note/models/transactions.py:115
|
#: apps/note/models/transactions.py:63
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -119,11 +120,11 @@ msgstr ""
|
|||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:61
|
#: apps/logs/models.py:61 apps/note/tables.py:147
|
||||||
msgid "edit"
|
msgid "edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/logs/models.py:62
|
#: apps/logs/models.py:62 apps/note/tables.py:151
|
||||||
msgid "delete"
|
msgid "delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -143,123 +144,123 @@ msgstr ""
|
|||||||
msgid "member"
|
msgid "member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:23
|
#: apps/member/models.py:25
|
||||||
msgid "phone number"
|
msgid "phone number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:29 templates/member/profile_detail.html:28
|
#: apps/member/models.py:31 templates/member/profile_detail.html:28
|
||||||
msgid "section"
|
msgid "section"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:30
|
#: apps/member/models.py:32
|
||||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:36 templates/member/profile_detail.html:31
|
#: apps/member/models.py:38 templates/member/profile_detail.html:31
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:42
|
#: apps/member/models.py:44
|
||||||
msgid "paid"
|
msgid "paid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:47 apps/member/models.py:48
|
#: apps/member/models.py:49 apps/member/models.py:50
|
||||||
msgid "user profile"
|
msgid "user profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:66
|
#: apps/member/models.py:68
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:71
|
#: apps/member/models.py:73
|
||||||
msgid "membership fee"
|
msgid "membership fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:75
|
#: apps/member/models.py:77
|
||||||
msgid "membership duration"
|
msgid "membership duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:76
|
#: apps/member/models.py:78
|
||||||
msgid "The longest time a membership can last (NULL = infinite)."
|
msgid "The longest time a membership can last (NULL = infinite)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:81
|
#: apps/member/models.py:83
|
||||||
msgid "membership start"
|
msgid "membership start"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:82
|
#: apps/member/models.py:84
|
||||||
msgid "How long after January 1st the members can renew their membership."
|
msgid "How long after January 1st the members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:87
|
#: apps/member/models.py:89
|
||||||
msgid "membership end"
|
msgid "membership end"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:88
|
#: apps/member/models.py:90
|
||||||
msgid ""
|
msgid ""
|
||||||
"How long the membership can last after January 1st of the next year after "
|
"How long the membership can last after January 1st of the next year after "
|
||||||
"members can renew their membership."
|
"members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:94 apps/note/models/notes.py:139
|
#: apps/member/models.py:96 apps/note/models/notes.py:139
|
||||||
msgid "club"
|
msgid "club"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:95
|
#: apps/member/models.py:97
|
||||||
msgid "clubs"
|
msgid "clubs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:118
|
#: apps/member/models.py:120 apps/permission/models.py:276
|
||||||
msgid "role"
|
msgid "role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:119
|
#: apps/member/models.py:121
|
||||||
msgid "roles"
|
msgid "roles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:143
|
#: apps/member/models.py:145
|
||||||
msgid "membership starts on"
|
msgid "membership starts on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:146
|
#: apps/member/models.py:148
|
||||||
msgid "membership ends on"
|
msgid "membership ends on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:150
|
#: apps/member/models.py:152
|
||||||
msgid "fee"
|
msgid "fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:154
|
#: apps/member/models.py:162
|
||||||
msgid "membership"
|
msgid "membership"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/models.py:155
|
#: apps/member/models.py:163
|
||||||
msgid "memberships"
|
msgid "memberships"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:69 templates/member/profile_detail.html:46
|
#: apps/member/views.py:80 templates/member/profile_detail.html:46
|
||||||
msgid "Update Profile"
|
msgid "Update Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:82
|
#: apps/member/views.py:93
|
||||||
msgid "An alias with a similar name already exists."
|
msgid "An alias with a similar name already exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:132
|
#: apps/member/views.py:146
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Account #%(id)s: %(username)s"
|
msgid "Account #%(id)s: %(username)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/member/views.py:202
|
#: apps/member/views.py:216
|
||||||
msgid "Alias successfully deleted"
|
msgid "Alias successfully deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
#: apps/note/admin.py:120 apps/note/models/transactions.py:95
|
||||||
msgid "source"
|
msgid "source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/admin.py:128 apps/note/admin.py:156
|
#: apps/note/admin.py:128 apps/note/admin.py:156
|
||||||
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:100
|
#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:108
|
||||||
msgid "destination"
|
msgid "destination"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -309,7 +310,7 @@ msgstr ""
|
|||||||
msgid "display image"
|
msgid "display image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:103
|
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:118
|
||||||
msgid "created at"
|
msgid "created at"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -383,116 +384,274 @@ msgstr ""
|
|||||||
msgid "You can't delete your main alias."
|
msgid "You can't delete your main alias."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:30
|
#: apps/note/models/transactions.py:31
|
||||||
msgid "transaction category"
|
msgid "transaction category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:31
|
#: apps/note/models/transactions.py:32
|
||||||
msgid "transaction categories"
|
msgid "transaction categories"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:47
|
#: apps/note/models/transactions.py:48
|
||||||
msgid "A template with this name already exist"
|
msgid "A template with this name already exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:111
|
#: apps/note/models/transactions.py:57 apps/note/models/transactions.py:126
|
||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:57
|
#: apps/note/models/transactions.py:58
|
||||||
msgid "in centimes"
|
msgid "in centimes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:75
|
#: apps/note/models/transactions.py:76
|
||||||
msgid "transaction template"
|
msgid "transaction template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:76
|
#: apps/note/models/transactions.py:77
|
||||||
msgid "transaction templates"
|
msgid "transaction templates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:107
|
#: apps/note/models/transactions.py:101 apps/note/models/transactions.py:114
|
||||||
|
#: apps/note/tables.py:33 apps/note/tables.py:42
|
||||||
|
msgid "used alias"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:122
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:117 templates/note/transaction_form.html:15
|
#: apps/note/models/transactions.py:130
|
||||||
msgid "Gift"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:118 templates/base.html:90
|
|
||||||
#: templates/note/transaction_form.html:19
|
|
||||||
#: templates/note/transaction_form.html:126
|
|
||||||
msgid "Transfer"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:119
|
|
||||||
msgid "Template"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:120 templates/note/transaction_form.html:23
|
|
||||||
msgid "Credit"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:121 templates/note/transaction_form.html:27
|
|
||||||
msgid "Debit"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:122 apps/note/models/transactions.py:230
|
|
||||||
msgid "membership transaction"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:129
|
|
||||||
msgid "reason"
|
msgid "reason"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:133
|
#: apps/note/models/transactions.py:135
|
||||||
msgid "valid"
|
msgid "valid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:138
|
#: apps/note/models/transactions.py:140 apps/note/tables.py:95
|
||||||
|
msgid "invalidity reason"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:147
|
||||||
msgid "transaction"
|
msgid "transaction"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:139
|
#: apps/note/models/transactions.py:148
|
||||||
msgid "transactions"
|
msgid "transactions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:207
|
#: apps/note/models/transactions.py:202 templates/base.html:83
|
||||||
|
#: templates/note/transaction_form.html:19
|
||||||
|
#: templates/note/transaction_form.html:145
|
||||||
|
msgid "Transfer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:188
|
||||||
|
msgid "Template"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:203
|
||||||
msgid "first_name"
|
msgid "first_name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:212
|
#: apps/note/models/transactions.py:208
|
||||||
msgid "bank"
|
msgid "bank"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:24
|
||||||
|
msgid "Credit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:28
|
||||||
|
msgid "Debit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:230 apps/note/models/transactions.py:235
|
||||||
|
msgid "membership transaction"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:231
|
#: apps/note/models/transactions.py:231
|
||||||
msgid "membership transactions"
|
msgid "membership transactions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/views.py:31
|
#: apps/note/views.py:39
|
||||||
msgid "Transfer money"
|
msgid "Transfer money"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/note/views.py:132 templates/base.html:78
|
#: apps/note/views.py:145 templates/base.html:79
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/__init__.py:61
|
#: apps/permission/models.py:69 apps/permission/models.py:262
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Can {type} {model}.{field} in {query}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/permission/models.py:71 apps/permission/models.py:264
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Can {type} {model} in {query}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/permission/models.py:84
|
||||||
|
msgid "rank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/permission/models.py:147
|
||||||
|
msgid "Specifying field applies only to view and change permission types."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/apps.py:11 templates/base.html:102
|
||||||
|
msgid "Treasury"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:56 apps/treasury/forms.py:95
|
||||||
|
#: templates/django_filters/rest_framework/form.html:5
|
||||||
|
#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:58
|
||||||
|
msgid "Close"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:65
|
||||||
|
msgid "Remittance is already closed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:70
|
||||||
|
msgid "You can't change the type of the remittance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:84
|
||||||
|
msgid "Last name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92
|
||||||
|
msgid "First name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98
|
||||||
|
msgid "Bank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:90 apps/treasury/tables.py:40
|
||||||
|
#: templates/note/transaction_form.html:128
|
||||||
|
#: templates/treasury/remittance_form.html:18
|
||||||
|
msgid "Amount"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:18
|
||||||
|
msgid "Invoice identifier"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:32
|
||||||
|
msgid "BDE"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:37
|
||||||
|
msgid "Object"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:41
|
||||||
|
msgid "Description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:46 templates/note/transaction_form.html:86
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:50
|
||||||
|
msgid "Address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:55
|
||||||
|
msgid "Place"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:59
|
||||||
|
msgid "Acquitted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:75
|
||||||
|
msgid "Designation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:79
|
||||||
|
msgid "Quantity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:83
|
||||||
|
msgid "Unit price"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:120
|
||||||
|
msgid "Date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:126
|
||||||
|
msgid "Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:131
|
||||||
|
msgid "Comment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:136
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:159
|
||||||
|
msgid "Remittance #{:d}: {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:178 apps/treasury/tables.py:64
|
||||||
|
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13
|
||||||
|
#: templates/treasury/remittance_list.html:13
|
||||||
|
msgid "Remittance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:16
|
||||||
|
msgid "Invoice #{:d}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10
|
||||||
|
#: templates/treasury/remittance_list.html:10
|
||||||
|
msgid "Invoice"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:38
|
||||||
|
msgid "Transaction count"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:43 apps/treasury/tables.py:45
|
||||||
|
msgid "View"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:66
|
||||||
|
msgid "Add"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:74
|
||||||
|
msgid "Remove"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: note_kfet/settings/__init__.py:63
|
||||||
msgid ""
|
msgid ""
|
||||||
"The Central Authentication Service grants you access to most of our websites "
|
"The Central Authentication Service grants you access to most of our websites "
|
||||||
"by authenticating only once, so you don't need to type your credentials "
|
"by authenticating only once, so you don't need to type your credentials "
|
||||||
"again unless your session expires or you logout."
|
"again unless your session expires or you logout."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:156
|
#: note_kfet/settings/base.py:151
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:157
|
#: note_kfet/settings/base.py:152
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:158
|
#: note_kfet/settings/base.py:153
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -500,18 +659,14 @@ msgstr ""
|
|||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/base.html:81
|
#: templates/base.html:87
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/base.html:84
|
#: templates/base.html:92
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/base.html:87
|
|
||||||
msgid "Buttons"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/cas_server/base.html:7
|
#: templates/cas_server/base.html:7
|
||||||
msgid "Central Authentication Service"
|
msgid "Central Authentication Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -567,11 +722,6 @@ msgstr ""
|
|||||||
msgid "Field filters"
|
msgid "Field filters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/django_filters/rest_framework/form.html:5
|
|
||||||
#: templates/member/club_form.html:10
|
|
||||||
msgid "Submit"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/member/club_detail.html:10
|
#: templates/member/club_detail.html:10
|
||||||
msgid "Membership starts on"
|
msgid "Membership starts on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -653,7 +803,7 @@ msgstr ""
|
|||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:38
|
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:50
|
||||||
msgid "Select emitters"
|
msgid "Select emitters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -681,49 +831,53 @@ msgstr ""
|
|||||||
msgid "Double consumptions"
|
msgid "Double consumptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/conso_form.html:141
|
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152
|
||||||
msgid "Recent transactions history"
|
msgid "Recent transactions history"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:55
|
#: templates/note/transaction_form.html:15
|
||||||
|
msgid "Gift"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:68
|
||||||
msgid "External payment"
|
msgid "External payment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:63
|
#: templates/note/transaction_form.html:76
|
||||||
msgid "Transfer type"
|
msgid "Transfer type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:73
|
#: templates/note/transaction_form.html:86
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:79
|
#: templates/note/transaction_form.html:92
|
||||||
msgid "First name"
|
msgid "First name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:85
|
#: templates/note/transaction_form.html:98
|
||||||
msgid "Bank"
|
msgid "Bank"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:97
|
#: templates/note/transaction_form.html:111
|
||||||
#: templates/note/transaction_form.html:179
|
#: templates/note/transaction_form.html:169
|
||||||
#: templates/note/transaction_form.html:186
|
#: templates/note/transaction_form.html:176
|
||||||
msgid "Select receivers"
|
msgid "Select receivers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:114
|
#: templates/note/transaction_form.html:128
|
||||||
msgid "Amount"
|
msgid "Amount"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:119
|
#: templates/note/transaction_form.html:138
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:193
|
#: templates/note/transaction_form.html:183
|
||||||
msgid "Credit note"
|
msgid "Credit note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:200
|
#: templates/note/transaction_form.html:190
|
||||||
msgid "Debit note"
|
msgid "Debit note"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -731,6 +885,22 @@ msgstr ""
|
|||||||
msgid "Buttons list"
|
msgid "Buttons list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:9
|
||||||
|
msgid "search button"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:20
|
||||||
|
msgid "buttons listing "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:71
|
||||||
|
msgid "button successfully deleted "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:75
|
||||||
|
msgid "Unable to delete button "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/logged_out.html:8
|
#: templates/registration/logged_out.html:8
|
||||||
msgid "Thanks for spending some quality time with the Web site today."
|
msgid "Thanks for spending some quality time with the Web site today."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -740,7 +910,7 @@ msgid "Log in again"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:7 templates/registration/login.html:8
|
#: templates/registration/login.html:7 templates/registration/login.html:8
|
||||||
#: templates/registration/login.html:26
|
#: templates/registration/login.html:28
|
||||||
#: templates/registration/password_reset_complete.html:10
|
#: templates/registration/password_reset_complete.html:10
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -752,7 +922,15 @@ msgid ""
|
|||||||
"page. Would you like to login to a different account?"
|
"page. Would you like to login to a different account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:27
|
#: templates/registration/login.html:22
|
||||||
|
msgid "You can also register via the central authentification server "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/registration/login.html:23
|
||||||
|
msgid "using this link "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/registration/login.html:29
|
||||||
msgid "Forgotten your password or username?"
|
msgid "Forgotten your password or username?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -808,3 +986,72 @@ msgstr ""
|
|||||||
#: templates/registration/password_reset_form.html:11
|
#: templates/registration/password_reset_form.html:11
|
||||||
msgid "Reset my password"
|
msgid "Reset my password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:6
|
||||||
|
msgid "Invoices list"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:42
|
||||||
|
msgid "Add product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:43
|
||||||
|
msgid "Remove product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_list.html:21
|
||||||
|
msgid "New invoice"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:7
|
||||||
|
msgid "Remittance #"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:9
|
||||||
|
#: templates/treasury/specialtransactionproxy_form.html:7
|
||||||
|
msgid "Remittances list"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:12
|
||||||
|
msgid "Count"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:29
|
||||||
|
msgid "Linked transactions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:34
|
||||||
|
msgid "There is no transaction linked with this remittance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:19
|
||||||
|
msgid "Opened remittances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:24
|
||||||
|
msgid "There is no opened remittance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:28
|
||||||
|
msgid "New remittance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:32
|
||||||
|
msgid "Transfers without remittances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:37
|
||||||
|
msgid "There is no transaction without any linked remittance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:43
|
||||||
|
msgid "Transfers with opened remittances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:48
|
||||||
|
msgid "There is no transaction with an opened linked remittance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:54
|
||||||
|
msgid "Closed remittances"
|
||||||
|
msgstr ""
|
||||||
|
@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-03-16 11:53+0100\n"
|
"POT-Creation-Date: 2020-03-26 14:40+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -18,9 +18,9 @@ msgid "activity"
|
|||||||
msgstr "activité"
|
msgstr "activité"
|
||||||
|
|
||||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||||
#: apps/member/models.py:61 apps/member/models.py:112
|
#: apps/member/models.py:63 apps/member/models.py:114
|
||||||
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
|
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25
|
||||||
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:202
|
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
|
||||||
#: templates/member/profile_detail.html:15
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "nom"
|
msgstr "nom"
|
||||||
@ -41,12 +41,13 @@ msgstr "type d'activité"
|
|||||||
msgid "activity types"
|
msgid "activity types"
|
||||||
msgstr "types d'activité"
|
msgstr "types d'activité"
|
||||||
|
|
||||||
#: apps/activity/models.py:48 apps/note/models/transactions.py:69
|
#: apps/activity/models.py:48 apps/note/models/transactions.py:70
|
||||||
|
#: apps/permission/models.py:91
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||||
#: apps/note/models/transactions.py:62 apps/note/models/transactions.py:115
|
#: apps/note/models/transactions.py:63
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr "type"
|
msgstr "type"
|
||||||
|
|
||||||
@ -114,11 +115,11 @@ msgstr "Nouvelles données"
|
|||||||
msgid "create"
|
msgid "create"
|
||||||
msgstr "Créer"
|
msgstr "Créer"
|
||||||
|
|
||||||
#: apps/logs/models.py:61
|
#: apps/logs/models.py:61 apps/note/tables.py:147
|
||||||
msgid "edit"
|
msgid "edit"
|
||||||
msgstr "Modifier"
|
msgstr "Modifier"
|
||||||
|
|
||||||
#: apps/logs/models.py:62
|
#: apps/logs/models.py:62 apps/note/tables.py:151
|
||||||
msgid "delete"
|
msgid "delete"
|
||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
@ -138,61 +139,61 @@ msgstr "Les logs ne peuvent pas être détruits."
|
|||||||
msgid "member"
|
msgid "member"
|
||||||
msgstr "adhérent"
|
msgstr "adhérent"
|
||||||
|
|
||||||
#: apps/member/models.py:23
|
#: apps/member/models.py:25
|
||||||
msgid "phone number"
|
msgid "phone number"
|
||||||
msgstr "numéro de téléphone"
|
msgstr "numéro de téléphone"
|
||||||
|
|
||||||
#: apps/member/models.py:29 templates/member/profile_detail.html:28
|
#: apps/member/models.py:31 templates/member/profile_detail.html:28
|
||||||
msgid "section"
|
msgid "section"
|
||||||
msgstr "section"
|
msgstr "section"
|
||||||
|
|
||||||
#: apps/member/models.py:30
|
#: apps/member/models.py:32
|
||||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||||
|
|
||||||
#: apps/member/models.py:36 templates/member/profile_detail.html:31
|
#: apps/member/models.py:38 templates/member/profile_detail.html:31
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr "adresse"
|
msgstr "adresse"
|
||||||
|
|
||||||
#: apps/member/models.py:42
|
#: apps/member/models.py:44
|
||||||
msgid "paid"
|
msgid "paid"
|
||||||
msgstr "payé"
|
msgstr "payé"
|
||||||
|
|
||||||
#: apps/member/models.py:47 apps/member/models.py:48
|
#: apps/member/models.py:49 apps/member/models.py:50
|
||||||
msgid "user profile"
|
msgid "user profile"
|
||||||
msgstr "profil utilisateur"
|
msgstr "profil utilisateur"
|
||||||
|
|
||||||
#: apps/member/models.py:66
|
#: apps/member/models.py:68
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr "courriel"
|
msgstr "courriel"
|
||||||
|
|
||||||
#: apps/member/models.py:71
|
#: apps/member/models.py:73
|
||||||
msgid "membership fee"
|
msgid "membership fee"
|
||||||
msgstr "cotisation pour adhérer"
|
msgstr "cotisation pour adhérer"
|
||||||
|
|
||||||
#: apps/member/models.py:75
|
#: apps/member/models.py:77
|
||||||
msgid "membership duration"
|
msgid "membership duration"
|
||||||
msgstr "durée de l'adhésion"
|
msgstr "durée de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:76
|
#: apps/member/models.py:78
|
||||||
msgid "The longest time a membership can last (NULL = infinite)."
|
msgid "The longest time a membership can last (NULL = infinite)."
|
||||||
msgstr "La durée maximale d'une adhésion (NULL = infinie)."
|
msgstr "La durée maximale d'une adhésion (NULL = infinie)."
|
||||||
|
|
||||||
#: apps/member/models.py:81
|
#: apps/member/models.py:83
|
||||||
msgid "membership start"
|
msgid "membership start"
|
||||||
msgstr "début de l'adhésion"
|
msgstr "début de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:82
|
#: apps/member/models.py:84
|
||||||
msgid "How long after January 1st the members can renew their membership."
|
msgid "How long after January 1st the members can renew their membership."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
|
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
|
||||||
"adhésion."
|
"adhésion."
|
||||||
|
|
||||||
#: apps/member/models.py:87
|
#: apps/member/models.py:89
|
||||||
msgid "membership end"
|
msgid "membership end"
|
||||||
msgstr "fin de l'adhésion"
|
msgstr "fin de l'adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:88
|
#: apps/member/models.py:90
|
||||||
msgid ""
|
msgid ""
|
||||||
"How long the membership can last after January 1st of the next year after "
|
"How long the membership can last after January 1st of the next year after "
|
||||||
"members can renew their membership."
|
"members can renew their membership."
|
||||||
@ -200,65 +201,65 @@ msgstr ""
|
|||||||
"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année "
|
"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année "
|
||||||
"suivante avant que les adhérents peuvent renouveler leur adhésion."
|
"suivante avant que les adhérents peuvent renouveler leur adhésion."
|
||||||
|
|
||||||
#: apps/member/models.py:94 apps/note/models/notes.py:139
|
#: apps/member/models.py:96 apps/note/models/notes.py:139
|
||||||
msgid "club"
|
msgid "club"
|
||||||
msgstr "club"
|
msgstr "club"
|
||||||
|
|
||||||
#: apps/member/models.py:95
|
#: apps/member/models.py:97
|
||||||
msgid "clubs"
|
msgid "clubs"
|
||||||
msgstr "clubs"
|
msgstr "clubs"
|
||||||
|
|
||||||
#: apps/member/models.py:118
|
#: apps/member/models.py:120 apps/permission/models.py:276
|
||||||
msgid "role"
|
msgid "role"
|
||||||
msgstr "rôle"
|
msgstr "rôle"
|
||||||
|
|
||||||
#: apps/member/models.py:119
|
#: apps/member/models.py:121
|
||||||
msgid "roles"
|
msgid "roles"
|
||||||
msgstr "rôles"
|
msgstr "rôles"
|
||||||
|
|
||||||
#: apps/member/models.py:143
|
#: apps/member/models.py:145
|
||||||
msgid "membership starts on"
|
msgid "membership starts on"
|
||||||
msgstr "l'adhésion commence le"
|
msgstr "l'adhésion commence le"
|
||||||
|
|
||||||
#: apps/member/models.py:146
|
#: apps/member/models.py:148
|
||||||
msgid "membership ends on"
|
msgid "membership ends on"
|
||||||
msgstr "l'adhésion finie le"
|
msgstr "l'adhésion finie le"
|
||||||
|
|
||||||
#: apps/member/models.py:150
|
#: apps/member/models.py:152
|
||||||
msgid "fee"
|
msgid "fee"
|
||||||
msgstr "cotisation"
|
msgstr "cotisation"
|
||||||
|
|
||||||
#: apps/member/models.py:154
|
#: apps/member/models.py:162
|
||||||
msgid "membership"
|
msgid "membership"
|
||||||
msgstr "adhésion"
|
msgstr "adhésion"
|
||||||
|
|
||||||
#: apps/member/models.py:155
|
#: apps/member/models.py:163
|
||||||
msgid "memberships"
|
msgid "memberships"
|
||||||
msgstr "adhésions"
|
msgstr "adhésions"
|
||||||
|
|
||||||
#: apps/member/views.py:69 templates/member/profile_detail.html:46
|
#: apps/member/views.py:80 templates/member/profile_detail.html:46
|
||||||
msgid "Update Profile"
|
msgid "Update Profile"
|
||||||
msgstr "Modifier le profil"
|
msgstr "Modifier le profil"
|
||||||
|
|
||||||
#: apps/member/views.py:82
|
#: apps/member/views.py:93
|
||||||
msgid "An alias with a similar name already exists."
|
msgid "An alias with a similar name already exists."
|
||||||
msgstr "Un alias avec un nom similaire existe déjà."
|
msgstr "Un alias avec un nom similaire existe déjà."
|
||||||
|
|
||||||
#: apps/member/views.py:132
|
#: apps/member/views.py:146
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Account #%(id)s: %(username)s"
|
msgid "Account #%(id)s: %(username)s"
|
||||||
msgstr "Compte n°%(id)s : %(username)s"
|
msgstr "Compte n°%(id)s : %(username)s"
|
||||||
|
|
||||||
#: apps/member/views.py:202
|
#: apps/member/views.py:216
|
||||||
msgid "Alias successfully deleted"
|
msgid "Alias successfully deleted"
|
||||||
msgstr "L'alias a bien été supprimé"
|
msgstr "L'alias a bien été supprimé"
|
||||||
|
|
||||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
#: apps/note/admin.py:120 apps/note/models/transactions.py:95
|
||||||
msgid "source"
|
msgid "source"
|
||||||
msgstr "source"
|
msgstr "source"
|
||||||
|
|
||||||
#: apps/note/admin.py:128 apps/note/admin.py:156
|
#: apps/note/admin.py:128 apps/note/admin.py:156
|
||||||
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:100
|
#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:108
|
||||||
msgid "destination"
|
msgid "destination"
|
||||||
msgstr "destination"
|
msgstr "destination"
|
||||||
|
|
||||||
@ -309,7 +310,7 @@ msgstr ""
|
|||||||
msgid "display image"
|
msgid "display image"
|
||||||
msgstr "image affichée"
|
msgstr "image affichée"
|
||||||
|
|
||||||
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:103
|
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:118
|
||||||
msgid "created at"
|
msgid "created at"
|
||||||
msgstr "créée le"
|
msgstr "créée le"
|
||||||
|
|
||||||
@ -383,116 +384,270 @@ msgstr "Un alias avec un nom similaire existe déjà : {}"
|
|||||||
msgid "You can't delete your main alias."
|
msgid "You can't delete your main alias."
|
||||||
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:30
|
#: apps/note/models/transactions.py:31
|
||||||
msgid "transaction category"
|
msgid "transaction category"
|
||||||
msgstr "catégorie de transaction"
|
msgstr "catégorie de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:31
|
#: apps/note/models/transactions.py:32
|
||||||
msgid "transaction categories"
|
msgid "transaction categories"
|
||||||
msgstr "catégories de transaction"
|
msgstr "catégories de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:47
|
#: apps/note/models/transactions.py:48
|
||||||
msgid "A template with this name already exist"
|
msgid "A template with this name already exist"
|
||||||
msgstr "Un modèle de transaction avec un nom similaire existe déjà."
|
msgstr "Un modèle de transaction avec un nom similaire existe déjà."
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:111
|
#: apps/note/models/transactions.py:57 apps/note/models/transactions.py:126
|
||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr "montant"
|
msgstr "montant"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:57
|
#: apps/note/models/transactions.py:58
|
||||||
msgid "in centimes"
|
msgid "in centimes"
|
||||||
msgstr "en centimes"
|
msgstr "en centimes"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:75
|
#: apps/note/models/transactions.py:76
|
||||||
msgid "transaction template"
|
msgid "transaction template"
|
||||||
msgstr "modèle de transaction"
|
msgstr "modèle de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:76
|
#: apps/note/models/transactions.py:77
|
||||||
msgid "transaction templates"
|
msgid "transaction templates"
|
||||||
msgstr "modèles de transaction"
|
msgstr "modèles de transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:107
|
#: apps/note/models/transactions.py:101 apps/note/models/transactions.py:114
|
||||||
|
#: apps/note/tables.py:33 apps/note/tables.py:42
|
||||||
|
msgid "used alias"
|
||||||
|
msgstr "alias utilisé"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:122
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "quantité"
|
msgstr "quantité"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:117 templates/note/transaction_form.html:15
|
#: apps/note/models/transactions.py:115
|
||||||
msgid "Gift"
|
|
||||||
msgstr "Don"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:118 templates/base.html:90
|
|
||||||
#: templates/note/transaction_form.html:19
|
|
||||||
#: templates/note/transaction_form.html:126
|
|
||||||
msgid "Transfer"
|
|
||||||
msgstr "Virement"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:119
|
|
||||||
msgid "Template"
|
|
||||||
msgstr "Bouton"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:120 templates/note/transaction_form.html:23
|
|
||||||
msgid "Credit"
|
|
||||||
msgstr "Crédit"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:121 templates/note/transaction_form.html:27
|
|
||||||
msgid "Debit"
|
|
||||||
msgstr "Retrait"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:122 apps/note/models/transactions.py:230
|
|
||||||
msgid "membership transaction"
|
|
||||||
msgstr "transaction d'adhésion"
|
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:129
|
|
||||||
msgid "reason"
|
msgid "reason"
|
||||||
msgstr "raison"
|
msgstr "raison"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:133
|
#: apps/note/models/transactions.py:119
|
||||||
msgid "valid"
|
msgid "valid"
|
||||||
msgstr "valide"
|
msgstr "valide"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:138
|
#: apps/note/models/transactions.py:124
|
||||||
msgid "transaction"
|
msgid "transaction"
|
||||||
msgstr "transaction"
|
msgstr "transaction"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:139
|
#: apps/note/models/transactions.py:125
|
||||||
msgid "transactions"
|
msgid "transactions"
|
||||||
msgstr "transactions"
|
msgstr "transactions"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:207
|
#: apps/note/models/transactions.py:168 templates/base.html:98
|
||||||
msgid "first_name"
|
#: templates/note/transaction_form.html:19
|
||||||
msgstr "Prénom"
|
#: templates/note/transaction_form.html:145
|
||||||
|
msgid "Transfer"
|
||||||
|
msgstr "Virement"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:212
|
#: apps/note/models/transactions.py:188
|
||||||
|
msgid "Template"
|
||||||
|
msgstr "Bouton"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:203
|
||||||
|
msgid "first_name"
|
||||||
|
msgstr "prénom"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:208
|
||||||
msgid "bank"
|
msgid "bank"
|
||||||
msgstr "Banque"
|
msgstr "banque"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:24
|
||||||
|
msgid "Credit"
|
||||||
|
msgstr "Crédit"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:28
|
||||||
|
msgid "Debit"
|
||||||
|
msgstr "Débit"
|
||||||
|
|
||||||
|
#: apps/note/models/transactions.py:230 apps/note/models/transactions.py:235
|
||||||
|
msgid "membership transaction"
|
||||||
|
msgstr "transaction d'adhésion"
|
||||||
|
|
||||||
#: apps/note/models/transactions.py:231
|
#: apps/note/models/transactions.py:231
|
||||||
msgid "membership transactions"
|
msgid "membership transactions"
|
||||||
msgstr "transactions d'adhésion"
|
msgstr "transactions d'adhésion"
|
||||||
|
|
||||||
#: apps/note/views.py:31
|
#: apps/note/views.py:39
|
||||||
msgid "Transfer money"
|
msgid "Transfer money"
|
||||||
msgstr "Transferts d'argent"
|
msgstr "Transférer de l'argent"
|
||||||
|
|
||||||
#: apps/note/views.py:132 templates/base.html:78
|
#: apps/note/views.py:145 templates/base.html:79
|
||||||
msgid "Consumptions"
|
msgid "Consumptions"
|
||||||
msgstr "Consommations"
|
msgstr "Consommations"
|
||||||
|
|
||||||
#: note_kfet/settings/__init__.py:61
|
#: apps/permission/models.py:69 apps/permission/models.py:262
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Can {type} {model}.{field} in {query}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/permission/models.py:71 apps/permission/models.py:264
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Can {type} {model} in {query}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/permission/models.py:84
|
||||||
|
msgid "rank"
|
||||||
|
msgstr "Rang"
|
||||||
|
|
||||||
|
#: apps/permission/models.py:147
|
||||||
|
msgid "Specifying field applies only to view and change permission types."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/treasury/apps.py:11 templates/base.html:102
|
||||||
|
msgid "Treasury"
|
||||||
|
msgstr "Trésorerie"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:56 apps/treasury/forms.py:95
|
||||||
|
#: templates/django_filters/rest_framework/form.html:5
|
||||||
|
#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:58
|
||||||
|
msgid "Close"
|
||||||
|
msgstr "Fermer"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:65
|
||||||
|
msgid "Remittance is already closed."
|
||||||
|
msgstr "La remise est déjà fermée."
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:70
|
||||||
|
msgid "You can't change the type of the remittance."
|
||||||
|
msgstr "Vous ne pouvez pas changer le type de la remise."
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:84
|
||||||
|
msgid "Last name"
|
||||||
|
msgstr "Nom de famille"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92
|
||||||
|
msgid "First name"
|
||||||
|
msgstr "Prénom"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98
|
||||||
|
msgid "Bank"
|
||||||
|
msgstr "Banque"
|
||||||
|
|
||||||
|
#: apps/treasury/forms.py:90 apps/treasury/tables.py:40
|
||||||
|
#: templates/note/transaction_form.html:128
|
||||||
|
#: templates/treasury/remittance_form.html:18
|
||||||
|
msgid "Amount"
|
||||||
|
msgstr "Montant"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:18
|
||||||
|
msgid "Invoice identifier"
|
||||||
|
msgstr "Numéro de facture"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:32
|
||||||
|
msgid "BDE"
|
||||||
|
msgstr "BDE"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:37
|
||||||
|
msgid "Object"
|
||||||
|
msgstr "Objet"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:41
|
||||||
|
msgid "Description"
|
||||||
|
msgstr "Description"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:46 templates/note/transaction_form.html:86
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:50
|
||||||
|
msgid "Address"
|
||||||
|
msgstr "Adresse"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:55
|
||||||
|
msgid "Place"
|
||||||
|
msgstr "Lieu"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:59
|
||||||
|
msgid "Acquitted"
|
||||||
|
msgstr "Acquittée"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:75
|
||||||
|
msgid "Designation"
|
||||||
|
msgstr "Désignation"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:79
|
||||||
|
msgid "Quantity"
|
||||||
|
msgstr "Quantité"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:83
|
||||||
|
msgid "Unit price"
|
||||||
|
msgstr "Prix unitaire"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:120
|
||||||
|
msgid "Date"
|
||||||
|
msgstr "Date"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:126
|
||||||
|
msgid "Type"
|
||||||
|
msgstr "Type"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:131
|
||||||
|
msgid "Comment"
|
||||||
|
msgstr "Commentaire"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:136
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Fermée"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:159
|
||||||
|
msgid "Remittance #{:d}: {}"
|
||||||
|
msgstr "Remise n°{:d} : {}"
|
||||||
|
|
||||||
|
#: apps/treasury/models.py:178 apps/treasury/tables.py:64
|
||||||
|
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13
|
||||||
|
#: templates/treasury/remittance_list.html:13
|
||||||
|
msgid "Remittance"
|
||||||
|
msgstr "Remise"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:16
|
||||||
|
msgid "Invoice #{:d}"
|
||||||
|
msgstr "Facture n°{:d}"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10
|
||||||
|
#: templates/treasury/remittance_list.html:10
|
||||||
|
msgid "Invoice"
|
||||||
|
msgstr "Facture"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:38
|
||||||
|
msgid "Transaction count"
|
||||||
|
msgstr "Nombre de transactions"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:43 apps/treasury/tables.py:45
|
||||||
|
msgid "View"
|
||||||
|
msgstr "Voir"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:66
|
||||||
|
msgid "Add"
|
||||||
|
msgstr "Ajouter"
|
||||||
|
|
||||||
|
#: apps/treasury/tables.py:74
|
||||||
|
msgid "Remove"
|
||||||
|
msgstr "supprimer"
|
||||||
|
|
||||||
|
#: note_kfet/settings/__init__.py:63
|
||||||
msgid ""
|
msgid ""
|
||||||
"The Central Authentication Service grants you access to most of our websites "
|
"The Central Authentication Service grants you access to most of our websites "
|
||||||
"by authenticating only once, so you don't need to type your credentials "
|
"by authenticating only once, so you don't need to type your credentials "
|
||||||
"again unless your session expires or you logout."
|
"again unless your session expires or you logout."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:156
|
#: note_kfet/settings/base.py:151
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:157
|
#: note_kfet/settings/base.py:152
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: note_kfet/settings/base.py:158
|
#: note_kfet/settings/base.py:153
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -500,18 +655,14 @@ msgstr ""
|
|||||||
msgid "The ENS Paris-Saclay BDE note."
|
msgid "The ENS Paris-Saclay BDE note."
|
||||||
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
||||||
|
|
||||||
#: templates/base.html:81
|
#: templates/base.html:87
|
||||||
msgid "Clubs"
|
msgid "Clubs"
|
||||||
msgstr "Clubs"
|
msgstr "Clubs"
|
||||||
|
|
||||||
#: templates/base.html:84
|
#: templates/base.html:92
|
||||||
msgid "Activities"
|
msgid "Activities"
|
||||||
msgstr "Activités"
|
msgstr "Activités"
|
||||||
|
|
||||||
#: templates/base.html:87
|
|
||||||
msgid "Buttons"
|
|
||||||
msgstr "Boutons"
|
|
||||||
|
|
||||||
#: templates/cas_server/base.html:7
|
#: templates/cas_server/base.html:7
|
||||||
msgid "Central Authentication Service"
|
msgid "Central Authentication Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -569,11 +720,6 @@ msgstr ""
|
|||||||
msgid "Field filters"
|
msgid "Field filters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/django_filters/rest_framework/form.html:5
|
|
||||||
#: templates/member/club_form.html:10
|
|
||||||
msgid "Submit"
|
|
||||||
msgstr "Envoyer"
|
|
||||||
|
|
||||||
#: templates/member/club_detail.html:10
|
#: templates/member/club_detail.html:10
|
||||||
msgid "Membership starts on"
|
msgid "Membership starts on"
|
||||||
msgstr "L'adhésion commence le"
|
msgstr "L'adhésion commence le"
|
||||||
@ -620,15 +766,15 @@ msgstr "Ajouter un alias"
|
|||||||
|
|
||||||
#: templates/member/profile_detail.html:15
|
#: templates/member/profile_detail.html:15
|
||||||
msgid "first name"
|
msgid "first name"
|
||||||
msgstr ""
|
msgstr "prénom"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:18
|
#: templates/member/profile_detail.html:18
|
||||||
msgid "username"
|
msgid "username"
|
||||||
msgstr ""
|
msgstr "pseudo"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:21
|
#: templates/member/profile_detail.html:21
|
||||||
msgid "password"
|
msgid "password"
|
||||||
msgstr ""
|
msgstr "mot de passe"
|
||||||
|
|
||||||
#: templates/member/profile_detail.html:24
|
#: templates/member/profile_detail.html:24
|
||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
@ -655,13 +801,13 @@ msgstr "Sauvegarder les changements"
|
|||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Inscription"
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:38
|
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:50
|
||||||
msgid "Select emitters"
|
msgid "Select emitters"
|
||||||
msgstr "Sélection des émetteurs"
|
msgstr "Sélection des émetteurs"
|
||||||
|
|
||||||
#: templates/note/conso_form.html:45
|
#: templates/note/conso_form.html:45
|
||||||
msgid "Select consumptions"
|
msgid "Select consumptions"
|
||||||
msgstr "Consommations"
|
msgstr "Sélection des consommations"
|
||||||
|
|
||||||
#: templates/note/conso_form.html:51
|
#: templates/note/conso_form.html:51
|
||||||
msgid "Consume!"
|
msgid "Consume!"
|
||||||
@ -677,55 +823,59 @@ msgstr "Éditer"
|
|||||||
|
|
||||||
#: templates/note/conso_form.html:126
|
#: templates/note/conso_form.html:126
|
||||||
msgid "Single consumptions"
|
msgid "Single consumptions"
|
||||||
msgstr "Consos simples"
|
msgstr "Consommations simples"
|
||||||
|
|
||||||
#: templates/note/conso_form.html:130
|
#: templates/note/conso_form.html:130
|
||||||
msgid "Double consumptions"
|
msgid "Double consumptions"
|
||||||
msgstr "Consos doubles"
|
msgstr "Consommations doubles"
|
||||||
|
|
||||||
#: templates/note/conso_form.html:141
|
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152
|
||||||
msgid "Recent transactions history"
|
msgid "Recent transactions history"
|
||||||
msgstr "Historique des transactions récentes"
|
msgstr "Historique des transactions récentes"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:55
|
#: templates/note/transaction_form.html:15
|
||||||
msgid "External payment"
|
msgid "Gift"
|
||||||
msgstr "Paiement extérieur"
|
msgstr "Don"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:63
|
#: templates/note/transaction_form.html:68
|
||||||
|
msgid "External payment"
|
||||||
|
msgstr "Paiement externe"
|
||||||
|
|
||||||
|
#: templates/note/transaction_form.html:76
|
||||||
msgid "Transfer type"
|
msgid "Transfer type"
|
||||||
msgstr "Type de transfert"
|
msgstr "Type de transfert"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:73
|
#: templates/note/transaction_form.html:86
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:79
|
#: templates/note/transaction_form.html:92
|
||||||
msgid "First name"
|
msgid "First name"
|
||||||
msgstr "Prénom"
|
msgstr "Prénom"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:85
|
#: templates/note/transaction_form.html:98
|
||||||
msgid "Bank"
|
msgid "Bank"
|
||||||
msgstr "Banque"
|
msgstr "Banque"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:97
|
#: templates/note/transaction_form.html:111
|
||||||
#: templates/note/transaction_form.html:179
|
#: templates/note/transaction_form.html:169
|
||||||
#: templates/note/transaction_form.html:186
|
#: templates/note/transaction_form.html:176
|
||||||
msgid "Select receivers"
|
msgid "Select receivers"
|
||||||
msgstr "Sélection des destinataires"
|
msgstr "Sélection des destinataires"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:114
|
#: templates/note/transaction_form.html:128
|
||||||
msgid "Amount"
|
msgid "Amount"
|
||||||
msgstr "Montant"
|
msgstr "Montant"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:119
|
#: templates/note/transaction_form.html:138
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
msgstr "Raison"
|
msgstr "Raison"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:193
|
#: templates/note/transaction_form.html:183
|
||||||
msgid "Credit note"
|
msgid "Credit note"
|
||||||
msgstr "Note à créditer"
|
msgstr "Note à recharger"
|
||||||
|
|
||||||
#: templates/note/transaction_form.html:200
|
#: templates/note/transaction_form.html:190
|
||||||
msgid "Debit note"
|
msgid "Debit note"
|
||||||
msgstr "Note à débiter"
|
msgstr "Note à débiter"
|
||||||
|
|
||||||
@ -733,6 +883,22 @@ msgstr "Note à débiter"
|
|||||||
msgid "Buttons list"
|
msgid "Buttons list"
|
||||||
msgstr "Liste des boutons"
|
msgstr "Liste des boutons"
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:9
|
||||||
|
msgid "search button"
|
||||||
|
msgstr "Chercher un bouton"
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:20
|
||||||
|
msgid "buttons listing "
|
||||||
|
msgstr "Liste des boutons"
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:71
|
||||||
|
msgid "button successfully deleted "
|
||||||
|
msgstr "Le bouton a bien été supprimé"
|
||||||
|
|
||||||
|
#: templates/note/transactiontemplate_list.html:75
|
||||||
|
msgid "Unable to delete button "
|
||||||
|
msgstr "Impossible de supprimer le bouton "
|
||||||
|
|
||||||
#: templates/registration/logged_out.html:8
|
#: templates/registration/logged_out.html:8
|
||||||
msgid "Thanks for spending some quality time with the Web site today."
|
msgid "Thanks for spending some quality time with the Web site today."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -742,7 +908,7 @@ msgid "Log in again"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:7 templates/registration/login.html:8
|
#: templates/registration/login.html:7 templates/registration/login.html:8
|
||||||
#: templates/registration/login.html:26
|
#: templates/registration/login.html:28
|
||||||
#: templates/registration/password_reset_complete.html:10
|
#: templates/registration/password_reset_complete.html:10
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -754,7 +920,15 @@ msgid ""
|
|||||||
"page. Would you like to login to a different account?"
|
"page. Would you like to login to a different account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/registration/login.html:27
|
#: templates/registration/login.html:22
|
||||||
|
msgid "You can also register via the central authentification server "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/registration/login.html:23
|
||||||
|
msgid "using this link "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/registration/login.html:29
|
||||||
msgid "Forgotten your password or username?"
|
msgid "Forgotten your password or username?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -810,3 +984,72 @@ msgstr ""
|
|||||||
#: templates/registration/password_reset_form.html:11
|
#: templates/registration/password_reset_form.html:11
|
||||||
msgid "Reset my password"
|
msgid "Reset my password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:6
|
||||||
|
msgid "Invoices list"
|
||||||
|
msgstr "Liste des factures"
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:42
|
||||||
|
msgid "Add product"
|
||||||
|
msgstr "Ajouter produit"
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_form.html:43
|
||||||
|
msgid "Remove product"
|
||||||
|
msgstr "Retirer produit"
|
||||||
|
|
||||||
|
#: templates/treasury/invoice_list.html:21
|
||||||
|
msgid "New invoice"
|
||||||
|
msgstr "Nouvelle facture"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:7
|
||||||
|
msgid "Remittance #"
|
||||||
|
msgstr "Remise n°"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:9
|
||||||
|
#: templates/treasury/specialtransactionproxy_form.html:7
|
||||||
|
msgid "Remittances list"
|
||||||
|
msgstr "Liste des remises"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:12
|
||||||
|
msgid "Count"
|
||||||
|
msgstr "Nombre"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:29
|
||||||
|
msgid "Linked transactions"
|
||||||
|
msgstr "Transactions liées"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_form.html:34
|
||||||
|
msgid "There is no transaction linked with this remittance."
|
||||||
|
msgstr "Il n'y a pas de transaction liée à cette remise."
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:19
|
||||||
|
msgid "Opened remittances"
|
||||||
|
msgstr "Remises ouvertes"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:24
|
||||||
|
msgid "There is no opened remittance."
|
||||||
|
msgstr "Il n'y a pas de remise ouverte."
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:28
|
||||||
|
msgid "New remittance"
|
||||||
|
msgstr "Nouvelle remise"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:32
|
||||||
|
msgid "Transfers without remittances"
|
||||||
|
msgstr "Transactions sans remise associée"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:37
|
||||||
|
msgid "There is no transaction without any linked remittance."
|
||||||
|
msgstr "Il n'y a pas de transactions sans remise associée."
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:43
|
||||||
|
msgid "Transfers with opened remittances"
|
||||||
|
msgstr "Transactions associées à une remise ouverte"
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:48
|
||||||
|
msgid "There is no transaction with an opened linked remittance."
|
||||||
|
msgstr "Il n'y a pas de transaction associée à une remise ouverte."
|
||||||
|
|
||||||
|
#: templates/treasury/remittance_list.html:54
|
||||||
|
msgid "Closed remittances"
|
||||||
|
msgstr "Remises fermées"
|
||||||
|
@ -59,6 +59,7 @@ INSTALLED_APPS = [
|
|||||||
'activity',
|
'activity',
|
||||||
'member',
|
'member',
|
||||||
'note',
|
'note',
|
||||||
|
'treasury',
|
||||||
'permission',
|
'permission',
|
||||||
'api',
|
'api',
|
||||||
'logs',
|
'logs',
|
||||||
|
@ -15,6 +15,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Include project routers
|
# Include project routers
|
||||||
path('note/', include('note.urls')),
|
path('note/', include('note.urls')),
|
||||||
|
path('treasury/', include('treasury.urls')),
|
||||||
|
|
||||||
# Include Django Contrib and Core routers
|
# Include Django Contrib and Core routers
|
||||||
path('i18n/', include('django.conf.urls.i18n')),
|
path('i18n/', include('django.conf.urls.i18n')),
|
||||||
|
@ -1 +1 @@
|
|||||||
psycopg2==2.8.4
|
psycopg2-binary==2.8.4
|
||||||
|
BIN
static/img/Finalist.png
Normal file
BIN
static/img/Finalist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 752 KiB |
BIN
static/img/Kataclist.png
Normal file
BIN
static/img/Kataclist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 664 KiB |
BIN
static/img/Listorique.png
Normal file
BIN
static/img/Listorique.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 414 KiB |
BIN
static/img/Monopolist.png
Normal file
BIN
static/img/Monopolist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 375 KiB |
BIN
static/img/Satellist.png
Normal file
BIN
static/img/Satellist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 202 KiB |
37
static/js/alias.js
Normal file
37
static/js/alias.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
$("#alias_input").on('keypress',function(e) {
|
||||||
|
if(e.which == 13) {
|
||||||
|
$("#alias_submit").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function create_alias(note_id){
|
||||||
|
$.post("/api/note/alias/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"name": $("#alias_input").val(),
|
||||||
|
"note": note_id
|
||||||
|
}
|
||||||
|
).done(function(){
|
||||||
|
$("#alias_table").load(location.href+ " #alias_table");
|
||||||
|
addMsg("Alias ajouté","success");
|
||||||
|
})
|
||||||
|
.fail(function(xhr, textStatus, error){
|
||||||
|
errMsg(xhr.responseJSON);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// on click of button "delete" , call the API
|
||||||
|
function delete_button(button_id){
|
||||||
|
$.ajax({
|
||||||
|
url:"/api/note/alias/"+button_id+"/",
|
||||||
|
method:"DELETE",
|
||||||
|
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
|
||||||
|
})
|
||||||
|
.done(function(){
|
||||||
|
addMsg('Alias supprimé','success');
|
||||||
|
$("#alias_table").load(location.href + " #alias_table");
|
||||||
|
})
|
||||||
|
.fail(function(xhr,textStatus, error){
|
||||||
|
errMsg(xhr.responseJSON);
|
||||||
|
});
|
||||||
|
}
|
@ -167,7 +167,7 @@ function reset() {
|
|||||||
function consumeAll() {
|
function consumeAll() {
|
||||||
notes_display.forEach(function(note_display) {
|
notes_display.forEach(function(note_display) {
|
||||||
buttons.forEach(function(button) {
|
buttons.forEach(function(button) {
|
||||||
consume(note_display.id, button.dest, button.quantity * note_display.quantity, button.amount,
|
consume(note_display.id, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount,
|
||||||
button.name + " (" + button.category_name + ")", button.type, button.category_id, button.id);
|
button.name + " (" + button.category_name + ")", button.type, button.category_id, button.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -176,6 +176,7 @@ function consumeAll() {
|
|||||||
/**
|
/**
|
||||||
* Create a new transaction from a button through the API.
|
* Create a new transaction from a button through the API.
|
||||||
* @param source The note that paid the item (type: int)
|
* @param source The note that paid the item (type: int)
|
||||||
|
* @param source_alias The alias used for the source (type: str)
|
||||||
* @param dest The note that sold the item (type: int)
|
* @param dest The note that sold the item (type: int)
|
||||||
* @param quantity The quantity sold (type: int)
|
* @param quantity The quantity sold (type: int)
|
||||||
* @param amount The price of one item, in cents (type: int)
|
* @param amount The price of one item, in cents (type: int)
|
||||||
@ -184,7 +185,7 @@ function consumeAll() {
|
|||||||
* @param category The category id of the button (type: int)
|
* @param category The category id of the button (type: int)
|
||||||
* @param template The button id (type: int)
|
* @param template The button id (type: int)
|
||||||
*/
|
*/
|
||||||
function consume(source, dest, quantity, amount, reason, type, category, template) {
|
function consume(source, source_alias, dest, quantity, amount, reason, type, category, template) {
|
||||||
$.post("/api/note/transaction/transaction/",
|
$.post("/api/note/transaction/transaction/",
|
||||||
{
|
{
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
@ -195,12 +196,32 @@ function consume(source, dest, quantity, amount, reason, type, category, templat
|
|||||||
"polymorphic_ctype": type,
|
"polymorphic_ctype": type,
|
||||||
"resourcetype": "RecurrentTransaction",
|
"resourcetype": "RecurrentTransaction",
|
||||||
"source": source,
|
"source": source,
|
||||||
|
"source_alias": source_alias,
|
||||||
"destination": dest,
|
"destination": dest,
|
||||||
"category": category,
|
"category": category,
|
||||||
"template": template
|
"template": template
|
||||||
}, reset).fail(function (e) {
|
}, reset).fail(function (e) {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": quantity,
|
||||||
|
"amount": amount,
|
||||||
|
"reason": reason,
|
||||||
|
"valid": false,
|
||||||
|
"invalidity_reason": "Solde insuffisant",
|
||||||
|
"polymorphic_ctype": type,
|
||||||
|
"resourcetype": "RecurrentTransaction",
|
||||||
|
"source": source,
|
||||||
|
"source_alias": source_alias,
|
||||||
|
"destination": dest,
|
||||||
|
"category": category,
|
||||||
|
"template": template
|
||||||
|
}).done(function() {
|
||||||
reset();
|
reset();
|
||||||
|
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger");
|
||||||
addMsg("Une erreur est survenue lors de la transaction : " + e.responseText, "danger");
|
}).fail(function () {
|
||||||
|
reset();
|
||||||
|
errMsg(e.responseJSON);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* jQuery Formset 1.3-pre
|
* jQuery Formset 1.5-pre
|
||||||
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
||||||
* @requires jQuery 1.2.6 or later
|
* @requires jQuery 1.2.6 or later
|
||||||
*
|
*
|
||||||
@ -55,19 +55,26 @@
|
|||||||
insertDeleteLink = function(row) {
|
insertDeleteLink = function(row) {
|
||||||
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
||||||
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
||||||
if (row.is('TR')) {
|
|
||||||
|
var delButtonHTML = '<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>';
|
||||||
|
if (options.deleteContainerClass) {
|
||||||
|
// If we have a specific container for the remove button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
|
||||||
|
} else if (row.is('TR')) {
|
||||||
// If the forms are laid out in table rows, insert
|
// If the forms are laid out in table rows, insert
|
||||||
// the remove button into the last table cell:
|
// the remove button into the last table cell:
|
||||||
row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');
|
row.children('td:last').append(delButtonHTML);
|
||||||
} else if (row.is('UL') || row.is('OL')) {
|
} else if (row.is('UL') || row.is('OL')) {
|
||||||
// If they're laid out as an ordered/unordered list,
|
// If they're laid out as an ordered/unordered list,
|
||||||
// insert an <li> after the last list item:
|
// insert an <li> after the last list item:
|
||||||
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');
|
row.append('<li>' + delButtonHTML + '</li>');
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just insert the remove button as the
|
// Otherwise, just insert the remove button as the
|
||||||
// last child element of the form's container:
|
// last child element of the form's container:
|
||||||
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');
|
row.append(delButtonHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
||||||
if (!showDeleteLinks()){
|
if (!showDeleteLinks()){
|
||||||
row.find('a.' + delCssSelector).hide();
|
row.find('a.' + delCssSelector).hide();
|
||||||
@ -156,6 +163,7 @@
|
|||||||
} else {
|
} else {
|
||||||
// Otherwise, use the last form in the formset; this works much better if you've got
|
// Otherwise, use the last form in the formset; this works much better if you've got
|
||||||
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
||||||
|
if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
|
||||||
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
||||||
template.find('input:hidden[id $= "-DELETE"]').remove();
|
template.find('input:hidden[id $= "-DELETE"]').remove();
|
||||||
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
||||||
@ -173,21 +181,28 @@
|
|||||||
// FIXME: Perhaps using $.data would be a better idea?
|
// FIXME: Perhaps using $.data would be a better idea?
|
||||||
options.formTemplate = template;
|
options.formTemplate = template;
|
||||||
|
|
||||||
if ($$.is('TR')) {
|
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
|
||||||
|
if (options.addContainerClass) {
|
||||||
|
// If we have a specific container for the "add" button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
var addContainer = $('[class*="' + options.addContainerClass + '"');
|
||||||
|
addContainer.append(addButtonHTML);
|
||||||
|
addButton = addContainer.find('[class="' + options.addCssClass + '"]');
|
||||||
|
} else if ($$.is('TR')) {
|
||||||
// If forms are laid out as table rows, insert the
|
// If forms are laid out as table rows, insert the
|
||||||
// "add" button in a new table row:
|
// "add" button in a new table row:
|
||||||
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
||||||
buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>')
|
buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add');
|
||||||
.addClass(options.formCssClass + '-add');
|
|
||||||
$$.parent().append(buttonRow);
|
$$.parent().append(buttonRow);
|
||||||
if (hideAddButton) buttonRow.hide();
|
|
||||||
addButton = buttonRow.find('a');
|
addButton = buttonRow.find('a');
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, insert it immediately after the last form:
|
// Otherwise, insert it immediately after the last form:
|
||||||
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
|
$$.filter(':last').after(addButtonHTML);
|
||||||
addButton = $$.filter(':last').next();
|
addButton = $$.filter(':last').next();
|
||||||
if (hideAddButton) addButton.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideAddButton) addButton.hide();
|
||||||
|
|
||||||
addButton.click(function() {
|
addButton.click(function() {
|
||||||
var formCount = parseInt(totalForms.val()),
|
var formCount = parseInt(totalForms.val()),
|
||||||
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
||||||
@ -220,12 +235,15 @@
|
|||||||
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
||||||
addText: 'add another', // Text for the add link
|
addText: 'add another', // Text for the add link
|
||||||
deleteText: 'remove', // Text for the delete link
|
deleteText: 'remove', // Text for the delete link
|
||||||
addCssClass: '', // CSS class applied to the add link
|
addContainerClass: null, // Container CSS class for the add link
|
||||||
deleteCssClass: '', // CSS class applied to the delete link
|
deleteContainerClass: null, // Container CSS class for the delete link
|
||||||
|
addCssClass: 'add-row', // CSS class applied to the add link
|
||||||
|
deleteCssClass: 'delete-row', // CSS class applied to the delete link
|
||||||
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
||||||
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
||||||
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
||||||
added: null, // Function called each time a new form is added
|
added: null, // Function called each time a new form is added
|
||||||
removed: null // Function called each time a form is deleted
|
removed: null, // Function called each time a form is deleted
|
||||||
|
hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
|
||||||
};
|
};
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
@ -39,10 +39,21 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
last.quantity = 1;
|
last.quantity = 1;
|
||||||
|
|
||||||
|
if (!last.note.user) {
|
||||||
|
$.getJSON("/api/note/note/" + last.note.id + "/?format=json", function(note) {
|
||||||
|
last.note.user = note.user;
|
||||||
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
||||||
$("#last_name").val(user.last_name);
|
$("#last_name").val(user.last_name);
|
||||||
$("#first_name").val(user.first_name);
|
$("#first_name").val(user.first_name);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
||||||
|
$("#last_name").val(user.last_name);
|
||||||
|
$("#first_name").val(user.first_name);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -72,12 +83,33 @@ $("#transfer").click(function() {
|
|||||||
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
"resourcetype": "Transaction",
|
"resourcetype": "Transaction",
|
||||||
"source": user_id,
|
"source": user_id,
|
||||||
"destination": dest.id
|
"destination": dest.id,
|
||||||
}, function () {
|
"destination_alias": dest.name
|
||||||
|
}).done(function () {
|
||||||
addMsg("Le transfert de "
|
addMsg("Le transfert de "
|
||||||
+ pretty_money(dest.quantity * 100 * $("#amount").val()) + " de votre note "
|
+ pretty_money(dest.quantity * 100 * $("#amount").val()) + " de votre note "
|
||||||
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}).fail(function () {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": dest.quantity,
|
||||||
|
"amount": 100 * $("#amount").val(),
|
||||||
|
"reason": $("#reason").val(),
|
||||||
|
"valid": false,
|
||||||
|
"invalidity_reason": "Solde insuffisant",
|
||||||
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
"resourcetype": "Transaction",
|
||||||
|
"source": user_id,
|
||||||
|
"destination": dest.id,
|
||||||
|
"destination_alias": dest.name
|
||||||
|
}).done(function () {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(dest.quantity * 100 * $("#amount").val()) + " de votre note "
|
||||||
|
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger");
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
addMsg("Le transfert de "
|
addMsg("Le transfert de "
|
||||||
@ -87,6 +119,7 @@ $("#transfer").click(function() {
|
|||||||
reset();
|
reset();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if ($("#type_transfer").is(':checked')) {
|
else if ($("#type_transfer").is(':checked')) {
|
||||||
sources_notes_display.forEach(function (source) {
|
sources_notes_display.forEach(function (source) {
|
||||||
@ -101,12 +134,35 @@ $("#transfer").click(function() {
|
|||||||
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
"resourcetype": "Transaction",
|
"resourcetype": "Transaction",
|
||||||
"source": source.id,
|
"source": source.id,
|
||||||
"destination": dest.id
|
"source_alias": source.name,
|
||||||
}, function () {
|
"destination": dest.id,
|
||||||
|
"destination_alias": dest.name
|
||||||
|
}).done(function () {
|
||||||
addMsg("Le transfert de "
|
addMsg("Le transfert de "
|
||||||
+ pretty_money(source.quantity * dest.quantity * 100 * $("#amount").val()) + " de la note " + source.name
|
+ pretty_money(source.quantity * dest.quantity * 100 * $("#amount").val()) + " de la note " + source.name
|
||||||
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
+ " vers la note " + dest.name + " a été fait avec succès !", "success");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}).fail(function (err) {
|
||||||
|
$.post("/api/note/transaction/transaction/",
|
||||||
|
{
|
||||||
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
"quantity": source.quantity * dest.quantity,
|
||||||
|
"amount": 100 * $("#amount").val(),
|
||||||
|
"reason": $("#reason").val(),
|
||||||
|
"valid": false,
|
||||||
|
"invalidity_reason": "Solde insuffisant",
|
||||||
|
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
"resourcetype": "Transaction",
|
||||||
|
"source": source.id,
|
||||||
|
"source_alias": source.name,
|
||||||
|
"destination": dest.id,
|
||||||
|
"destination_alias": dest.name
|
||||||
|
}).done(function () {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(source.quantity * dest.quantity * 100 * $("#amount").val()) + " de la note " + source.name
|
||||||
|
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger");
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
addMsg("Le transfert de "
|
addMsg("Le transfert de "
|
||||||
@ -117,6 +173,7 @@ $("#transfer").click(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
||||||
let special_note = $("#credit_type").val();
|
let special_note = $("#credit_type").val();
|
||||||
let user_note = dests_notes_display[0].id;
|
let user_note = dests_notes_display[0].id;
|
||||||
@ -146,15 +203,17 @@ $("#transfer").click(function() {
|
|||||||
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
"resourcetype": "SpecialTransaction",
|
"resourcetype": "SpecialTransaction",
|
||||||
"source": source,
|
"source": source,
|
||||||
|
"source_alias": source.name,
|
||||||
"destination": dest,
|
"destination": dest,
|
||||||
|
"destination_alias": dest.name,
|
||||||
"last_name": $("#last_name").val(),
|
"last_name": $("#last_name").val(),
|
||||||
"first_name": $("#first_name").val(),
|
"first_name": $("#first_name").val(),
|
||||||
"bank": $("#bank").val()
|
"bank": $("#bank").val()
|
||||||
}, function () {
|
}).done(function () {
|
||||||
addMsg("Le crédit/retrait a bien été effectué !", "success");
|
addMsg("Le crédit/retrait a bien été effectué !", "success");
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
addMsg("Le crédit/transfert a échoué : " + err.responseText, "danger");
|
addMsg("Le crédit/retrait a échoué : " + err.responseText, "danger");
|
||||||
reset();
|
reset();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if "note.transaction"|not_empty_model_list %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if "member.club"|not_empty_model_list %}
|
{% if "member.club"|not_empty_model_list %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
||||||
@ -89,14 +94,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "note.transactiontemplate"|not_empty_model_change_list %}
|
{% if "treasury.invoice"|not_empty_model_change_list %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> {% trans 'Buttons' %}</a>
|
<a class="nav-link" href="{% url 'treasury:invoice_list' %}"><i class="fa fa-money"></i>{% trans 'Treasury' %} </a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item active">
|
|
||||||
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "member/noteowner_detail.html" %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
|
||||||
|
{% block profile_info %}
|
||||||
|
{% include "member/club_info.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block profile_content %}
|
||||||
|
|
||||||
<form method="post" action="">
|
<form method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -10,9 +14,9 @@
|
|||||||
<input type="submit" name="submit" value="Add Members" class="btn btn-primary" id="submit-save">
|
<input type="submit" name="submit" value="Add Members" class="btn btn-primary" id="submit-save">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Include formset plugin - including jQuery dependency -->
|
{% block extrajavascript %}
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
|
||||||
<script src="{% static 'js/dynamic-formset.js' %}"></script>
|
<script src="{% static 'js/dynamic-formset.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
$('.formset-row').formset({
|
$('.formset-row').formset({
|
||||||
|
12
templates/member/alias_update.html
Normal file
12
templates/member/alias_update.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% load django_tables2 crispy_forms_tags i18n %}
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<input id="alias_input" type="text" value=""/>
|
||||||
|
<button id="alias_submit" class="btn btn-primary mx-2" onclick="create_alias( {{ object.note.pk }} )" type="submit">
|
||||||
|
{% trans "Add alias" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
{% render_table aliases %}
|
||||||
|
</div>
|
||||||
|
</div>
|
10
templates/member/club_alias.html
Normal file
10
templates/member/club_alias.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "member/club_detail.html" %}
|
||||||
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block profile_content %}
|
||||||
|
{% include "member/alias_update.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script src="/static/js/alias.js"></script>
|
||||||
|
{% endblock%}
|
@ -1,62 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "member/noteowner_detail.html" %}
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load pretty_money %}
|
|
||||||
{% block content %}
|
|
||||||
<p><a class="btn btn-primary" href="{% url 'member:club_list' %}">Clubs</a></p>
|
|
||||||
<h3 class="text-center"> Club {{ object.name }}</h3>
|
|
||||||
<dl>
|
|
||||||
<dt>{% trans 'Membership starts on' %}</dt>
|
|
||||||
<dd>{{ club.membership_start }}</dd>
|
|
||||||
<dt>{% trans 'Membership ends on' %}</dt>
|
|
||||||
<dd>{{ club.membership_end }}</dd>
|
|
||||||
<dt>{% trans 'Membership duration' %}</dt>
|
|
||||||
<dd>{{ club.membership_duration }}</dd>
|
|
||||||
<dt> Aliases </dt>
|
|
||||||
<dd>{{ club.note.aliases_set.all }}</dd>
|
|
||||||
<dt>{% trans 'balance' %}</dt>
|
|
||||||
<dd>{{ club.note.balance | pretty_money }}</dd>
|
|
||||||
|
|
||||||
</dl>
|
{% block profile_info %}
|
||||||
|
{% include "member/club_info.html" %}
|
||||||
|
{% endblock %}
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Ajouter des membres </a>
|
{% block profile_content %}
|
||||||
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Modifier les informations </a>
|
{% include "member/club_tables.html" %}
|
||||||
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Ajouter des roles </a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="accordion" id="accordionExample">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header" id="headingOne">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
|
||||||
<i class="fa fa-users"></i> Membres du club
|
|
||||||
</button>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
{% render_table member_list %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header" id="headingTwo">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
|
||||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
|
||||||
</button>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
|
|
||||||
<div class="card-body">
|
|
||||||
{% render_table history_list %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Clubs list" %}</a></p>
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form|crispy}}
|
{{form|crispy}}
|
||||||
|
46
templates/member/club_info.html
Normal file
46
templates/member/club_info.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% load i18n static pretty_money %}
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4> Club {{ club.name }} </h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-top text-center">
|
||||||
|
<a href="{% url 'member:club_update_pic' club.pk %}">
|
||||||
|
<img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2" >
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="profile_infos">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ club.name}}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt>
|
||||||
|
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ club.membership_start }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'membership end'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ club.membership_end }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'membership duration'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ club.membership_duration }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ club.membership_fee|pretty_money }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6"><a href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||||
|
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-3">{% trans 'email'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-9"><a href="mailto:{{ club.email }}">{{ club.email}}</a></dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_add_member' pk=club.pk %}"> {% trans "Add member" %}</a>
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_update' pk=club.pk %}"> {% trans "Edit" %}</a>
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_add_member' pk=club.pk %}"> {% trans "Add roles" %}</a>
|
||||||
|
{% url 'member:club_detail' club.pk as club_detail_url %}
|
||||||
|
{%if request.get_full_path != club_detail_url %}
|
||||||
|
<a class="btn btn-primary btn-sm my-1" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
||||||
|
{% endif %} </div>
|
||||||
|
</div>
|
@ -2,15 +2,65 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="row justify-content-center mb-4">
|
||||||
|
<div class="col-md-10 text-center">
|
||||||
|
<h4>
|
||||||
|
{% trans "search clubs" %}
|
||||||
|
</h4>
|
||||||
|
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/>
|
||||||
|
<hr>
|
||||||
|
<a class="btn btn-primary text-center my-4" href="{% url 'member:club_create' %}">{% trans "Créer un club" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="card card-border shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h5> {% trans "club listing "%}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-0 py-0" id="club_table">
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
|
</div>
|
||||||
<a class="btn btn-primary" href="{% url 'member:club_create' %}">{% trans "New club" %}</a>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function getInfo() {
|
||||||
|
var asked = $("#search_field").val();
|
||||||
|
/* on ne fait la requête que si on a au moins un caractère pour chercher */
|
||||||
|
var sel = $(".table-row");
|
||||||
|
if (asked.length >= 1) {
|
||||||
|
$.getJSON("/api/members/club/?format=json&search="+asked, function(buttons){
|
||||||
|
let selected_id = buttons.results.map((a => "#row-"+a.id));
|
||||||
|
console.log(selected_id.join());
|
||||||
|
$(".table-row,"+selected_id.join()).show();
|
||||||
|
$(".table-row").not(selected_id.join()).hide();
|
||||||
|
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
// show everything
|
||||||
|
$('table tr').show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timer;
|
||||||
|
var timer_on;
|
||||||
|
/* Fontion appelée quand le texte change (délenche le timer) */
|
||||||
|
function search_field_moved(secondfield) {
|
||||||
|
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||||
|
}
|
||||||
|
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
|
||||||
|
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||||
|
timer_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clickable row
|
||||||
$(document).ready(function($) {
|
$(document).ready(function($) {
|
||||||
$(".table-row").click(function() {
|
$(".table-row").click(function() {
|
||||||
window.document.location = $(this).data("href");
|
window.document.location = $(this).data("href");
|
||||||
|
6
templates/member/club_picture_update.html
Normal file
6
templates/member/club_picture_update.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "member/club_detail.html" %}
|
||||||
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block profile_content%}
|
||||||
|
{% include "member/picture_update.html" %}
|
||||||
|
{% endblock%}
|
31
templates/member/club_tables.html
Normal file
31
templates/member/club_tables.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
<div class="accordion shadow" id="accordionProfile">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
|
<a class="btn btn-link stretched-link font-weight-bold"
|
||||||
|
data-toggle="collapse" data-target="#clubListCollapse"
|
||||||
|
aria-expanded="true" aria-controls="clubListCollapse">
|
||||||
|
<i class="fa fa-users"></i> {% trans "Member of the Club" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="clubListCollapse" class="collapse show" style="overflow:auto hidden" aria-labelledby="clubListHeading" data-parent="#accordionProfile">
|
||||||
|
{% render_table member_list %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
|
<a class="btn btn-link stretched-link collapsed font-weight-bold"
|
||||||
|
data-toggle="collapse" data-target="#historyListCollapse"
|
||||||
|
aria-expanded="false" aria-controls="historyListCollapse">
|
||||||
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="historyListCollapse" class="collapse" style="overflow:auto hidden" aria-labelledby="historyListHeading" data-parent="#accordionProfile">
|
||||||
|
<div id="history_list">
|
||||||
|
{% render_table history_list %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
27
templates/member/noteowner_detail.html
Normal file
27
templates/member/noteowner_detail.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load pretty_money %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-3 mb-4">
|
||||||
|
{% block profile_info %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
{% block profile_content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
function refreshhistory() {
|
||||||
|
$("#history_list").load("{% url 'member:user_detail' pk=object.pk %} #history_list");
|
||||||
|
$("#profile_infos").load("{% url 'member:user_detail' pk=object.pk %} #profile_infos");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
95
templates/member/picture_update.html
Normal file
95
templates/member/picture_update.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{% load i18n crispy_forms_tags %}
|
||||||
|
{% block profile_content %}
|
||||||
|
<div class="text-center">
|
||||||
|
<form method="post" enctype="multipart/form-data" id="formUpload">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form |crispy }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- MODAL TO CROP THE IMAGE -->
|
||||||
|
<div class="modal fade" id="modalCrop">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<img src="" id="modal-image" style="max-width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="btn-group pull-left" role="group">
|
||||||
|
<button type="button" class="btn btn-default" id="js-zoom-in">
|
||||||
|
<span class="glyphicon glyphicon-zoom-in"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default js-zoom-out">
|
||||||
|
<span class="glyphicon glyphicon-zoom-out"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Nevermind</button>
|
||||||
|
<button type="button" class="btn btn-primary js-crop-and-upload">Crop and upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block extracss %}
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript%}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery-cropper@1.0.1/dist/jquery-cropper.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
/* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
|
||||||
|
$("#id_image").change(function (e) {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
$("#modal-image").attr("src", e.target.result);
|
||||||
|
$("#modalCrop").modal("show");
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* SCRIPTS TO HANDLE THE CROPPER BOX */
|
||||||
|
var $image = $("#modal-image");
|
||||||
|
var cropBoxData;
|
||||||
|
var canvasData;
|
||||||
|
$("#modalCrop").on("shown.bs.modal", function () {
|
||||||
|
$image.cropper({
|
||||||
|
viewMode: 1,
|
||||||
|
aspectRatio: 1/1,
|
||||||
|
minCropBoxWidth: 200,
|
||||||
|
minCropBoxHeight: 200,
|
||||||
|
ready: function () {
|
||||||
|
$image.cropper("setCanvasData", canvasData);
|
||||||
|
$image.cropper("setCropBoxData", cropBoxData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on("hidden.bs.modal", function () {
|
||||||
|
cropBoxData = $image.cropper("getCropBoxData");
|
||||||
|
canvasData = $image.cropper("getCanvasData");
|
||||||
|
$image.cropper("destroy");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".js-zoom-in").click(function () {
|
||||||
|
$image.cropper("zoom", 0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".js-zoom-out").click(function () {
|
||||||
|
$image.cropper("zoom", -0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* SCRIPT TO COLLECT THE DATA AND POST TO THE SERVER */
|
||||||
|
$(".js-crop-and-upload").click(function () {
|
||||||
|
var cropData = $image.cropper("getData");
|
||||||
|
$("#id_x").val(cropData["x"]);
|
||||||
|
$("#id_y").val(cropData["y"]);
|
||||||
|
$("#id_height").val(cropData["height"]);
|
||||||
|
$("#id_width").val(cropData["width"]);
|
||||||
|
$("#formUpload").submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -2,18 +2,9 @@
|
|||||||
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
{% block profile_content %}
|
{% block profile_content %}
|
||||||
<div class="d-flex justify-content-center">
|
{% include "member/alias_update.html"%}
|
||||||
<form class=" text-center form my-2" action="" method="post">
|
{% endblock %}
|
||||||
{% csrf_token %}
|
|
||||||
{{ form |crispy }}
|
{% block extrajavascript %}
|
||||||
<button class="btn btn-primary mx-2" type="submit">
|
<script src="/static/js/alias.js"></script>
|
||||||
{% trans "Add alias" %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="card bg-light shadow">
|
|
||||||
<div class="card-body">
|
|
||||||
{% render_table aliases %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
|
@ -1,97 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "member/noteowner_detail.html" %}
|
||||||
{% load i18n static pretty_money django_tables2 %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block profile_info %}
|
||||||
<div class="row mt-4">
|
{% include "member/profile_info.html" %}
|
||||||
<div class="col-md-3 mb-4">
|
{% endblock %}
|
||||||
<div class="card bg-light shadow">
|
|
||||||
<div class="card-top text-center">
|
|
||||||
<a href="{% url 'member:user_update_pic' object.pk %}">
|
|
||||||
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="card-body" id="profile_infos">
|
|
||||||
<dl class="row">
|
|
||||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'username'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ object.username }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">
|
|
||||||
<a class="small" href="{% url 'password_change' %}">
|
|
||||||
{% trans 'Change password' %}
|
|
||||||
</a>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ object.profile.section }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ object.profile.address }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
|
||||||
<dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
|
|
||||||
|
|
||||||
<dt class="col-xl-6"> <a href="{% url 'member:user_alias' object.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
|
||||||
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
{% if object.pk == user.pk %}
|
|
||||||
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="card-footer text-center">
|
|
||||||
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
|
||||||
{% url 'member:user_detail' object.pk as user_profile_url %}
|
|
||||||
{%if request.get_full_path != user_profile_url %}
|
|
||||||
<a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-9">
|
|
||||||
{% block profile_content %}
|
{% block profile_content %}
|
||||||
<div class="accordion shadow" id="accordionProfile">
|
{% include "member/profile_tables.html" %}
|
||||||
<div class="card">
|
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
|
||||||
<a class="btn btn-link stretched-link font-weight-bold"
|
|
||||||
data-toggle="collapse" data-target="#clubListCollapse"
|
|
||||||
aria-expanded="true" aria-controls="clubListCollapse">
|
|
||||||
<i class="fa fa-users"></i> {% trans "View my memberships" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div id="clubListCollapse" class="collapse show" style="overflow:auto hidden" aria-labelledby="clubListHeading" data-parent="#accordionProfile">
|
|
||||||
{% render_table club_list %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header position-relative" id="historyListHeading">
|
|
||||||
<a class="btn btn-link stretched-link collapsed font-weight-bold"
|
|
||||||
data-toggle="collapse" data-target="#historyListCollapse"
|
|
||||||
aria-expanded="false" aria-controls="historyListCollapse">
|
|
||||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div id="historyListCollapse" class="collapse" style="overflow:auto hidden" aria-labelledby="historyListHeading" data-parent="#accordionProfile">
|
|
||||||
<div id="history_list">
|
|
||||||
{% render_table history_list %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extrajavascript %}
|
|
||||||
<script>
|
|
||||||
function refreshHistory() {
|
|
||||||
$("#history_list").load("{% url 'member:user_detail' pk=object.pk %} #history_list");
|
|
||||||
$("#profile_infos").load("{% url 'member:user_detail' pk=object.pk %} #profile_infos");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
51
templates/member/profile_info.html
Normal file
51
templates/member/profile_info.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% load i18n static pretty_money %}
|
||||||
|
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-header text-center" >
|
||||||
|
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-top text-center">
|
||||||
|
<a href="{% url 'member:user_update_pic' object.pk %}">
|
||||||
|
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="profile_infos">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'username'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.username }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">
|
||||||
|
<a class="small" href="{% url 'password_change' %}">
|
||||||
|
{% trans 'Change password' %}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.profile.section }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.profile.address }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||||
|
<dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-xl-6"> <a href="{% url 'member:user_alias' object.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||||
|
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
{% if object.pk == user.pk %}
|
||||||
|
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
||||||
|
{% url 'member:user_detail' object.pk as user_profile_url %}
|
||||||
|
{%if request.get_full_path != user_profile_url %}
|
||||||
|
<a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -2,96 +2,5 @@
|
|||||||
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
{% block profile_content%}
|
{% block profile_content%}
|
||||||
<div class="text-center">
|
{% include "member/picture_update.html" %}
|
||||||
<form method="post" enctype="multipart/form-data" id="formUpload">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form |crispy }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- MODAL TO CROP THE IMAGE -->
|
|
||||||
<div class="modal fade" id="modalCrop">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-body">
|
|
||||||
<img src="" id="modal-image" style="max-width: 100%;">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<div class="btn-group pull-left" role="group">
|
|
||||||
<button type="button" class="btn btn-default" id="js-zoom-in">
|
|
||||||
<span class="glyphicon glyphicon-zoom-in"></span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-default js-zoom-out">
|
|
||||||
<span class="glyphicon glyphicon-zoom-out"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Nevermind</button>
|
|
||||||
<button type="button" class="btn btn-primary js-crop-and-upload">Crop and upload</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% block extracss %}
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css" rel="stylesheet">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extrajavascript%}
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery-cropper@1.0.1/dist/jquery-cropper.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
/* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
|
|
||||||
$("#id_image").change(function (e) {
|
|
||||||
if (this.files && this.files[0]) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function (e) {
|
|
||||||
$("#modal-image").attr("src", e.target.result);
|
|
||||||
$("#modalCrop").modal("show");
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(this.files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* SCRIPTS TO HANDLE THE CROPPER BOX */
|
|
||||||
var $image = $("#modal-image");
|
|
||||||
var cropBoxData;
|
|
||||||
var canvasData;
|
|
||||||
$("#modalCrop").on("shown.bs.modal", function () {
|
|
||||||
$image.cropper({
|
|
||||||
viewMode: 1,
|
|
||||||
aspectRatio: 1/1,
|
|
||||||
minCropBoxWidth: 200,
|
|
||||||
minCropBoxHeight: 200,
|
|
||||||
ready: function () {
|
|
||||||
$image.cropper("setCanvasData", canvasData);
|
|
||||||
$image.cropper("setCropBoxData", cropBoxData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).on("hidden.bs.modal", function () {
|
|
||||||
cropBoxData = $image.cropper("getCropBoxData");
|
|
||||||
canvasData = $image.cropper("getCanvasData");
|
|
||||||
$image.cropper("destroy");
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".js-zoom-in").click(function () {
|
|
||||||
$image.cropper("zoom", 0.1);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".js-zoom-out").click(function () {
|
|
||||||
$image.cropper("zoom", -0.1);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* SCRIPT TO COLLECT THE DATA AND POST TO THE SERVER */
|
|
||||||
$(".js-crop-and-upload").click(function () {
|
|
||||||
var cropData = $image.cropper("getData");
|
|
||||||
$("#id_x").val(cropData["x"]);
|
|
||||||
$("#id_y").val(cropData["y"]);
|
|
||||||
$("#id_height").val(cropData["height"]);
|
|
||||||
$("#id_width").val(cropData["width"]);
|
|
||||||
$("#formUpload").submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
|
31
templates/member/profile_tables.html
Normal file
31
templates/member/profile_tables.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
<div class="accordion shadow" id="accordionProfile">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
|
<a class="btn btn-link stretched-link font-weight-bold"
|
||||||
|
data-toggle="collapse" data-target="#clubListCollapse"
|
||||||
|
aria-expanded="true" aria-controls="clubListCollapse">
|
||||||
|
<i class="fa fa-users"></i> {% trans "View my memberships" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="clubListCollapse" class="collapse show" style="overflow:auto hidden" aria-labelledby="clubListHeading" data-parent="#accordionProfile">
|
||||||
|
{% render_table club_list %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
|
<a class="btn btn-link stretched-link collapsed font-weight-bold"
|
||||||
|
data-toggle="collapse" data-target="#historyListCollapse"
|
||||||
|
aria-expanded="false" aria-controls="historyListCollapse">
|
||||||
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="historyListCollapse" class="collapse" style="overflow:auto hidden" aria-labelledby="historyListHeading" data-parent="#accordionProfile">
|
||||||
|
<div id="history_list">
|
||||||
|
{% render_table history_list %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,23 +1,79 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load pretty_money %}
|
{% load pretty_money %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="row justify-content-center mb-4">
|
||||||
<table class="table">
|
<div class="col-md-10 text-center">
|
||||||
<tr>
|
<h4>
|
||||||
<td>ID</td><td>Nom</td>
|
{% trans "search button" %}
|
||||||
<td>Destinataire</td>
|
</h4>
|
||||||
<td>Montant</td>
|
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/>
|
||||||
<td>Catégorie</td>
|
<hr>
|
||||||
</tr>
|
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}">{% trans "New button" %}</a>
|
||||||
{% for object in object_list %}
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>{{object.pk}}</td>
|
<div class="row justify-content-center">
|
||||||
<td><a href="{{object.get_absolute_url}}">{{ object.name }}</a></td>
|
<div class="col-md-10">
|
||||||
<td>{{ object.destination }}</td>
|
<div class="card card-border shadow">
|
||||||
<td>{{ object.amount | pretty_money }}</td>
|
<div class="card-header text-center">
|
||||||
<td>{{ object.category }}</td>
|
<h5> {% trans "buttons listing "%}</h5>
|
||||||
</tr>
|
</div>
|
||||||
{% endfor %}
|
<div class="card-body px-0 py-0" id="buttons_table">
|
||||||
</table>
|
{% render_table table %}
|
||||||
<a class="btn btn-primary" href="{% url 'note:template_create' %}">Créer un bouton</a>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
/* fonction appelée à la fin du timer */
|
||||||
|
function getInfo() {
|
||||||
|
var asked = $("#search_field").val();
|
||||||
|
/* on ne fait la requête que si on a au moins un caractère pour chercher */
|
||||||
|
var sel = $(".table-row");
|
||||||
|
if (asked.length >= 1) {
|
||||||
|
$.getJSON("/api/note/transaction/template/?format=json&search="+asked, function(buttons){
|
||||||
|
let selected_id = buttons.results.map((a => "#row-"+a.id));
|
||||||
|
console.log(selected_id.join());
|
||||||
|
$(".table-row,"+selected_id.join()).show();
|
||||||
|
$(".table-row").not(selected_id.join()).hide();
|
||||||
|
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
// show everything
|
||||||
|
$('table tr').show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timer;
|
||||||
|
var timer_on;
|
||||||
|
/* Fontion appelée quand le texte change (délenche le timer) */
|
||||||
|
function search_field_moved(secondfield) {
|
||||||
|
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||||
|
}
|
||||||
|
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
|
||||||
|
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||||
|
timer_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// on click of button "delete" , call the API
|
||||||
|
function delete_button(button_id){
|
||||||
|
$.ajax({
|
||||||
|
url:"/api/note/transaction/template/"+button_id+"/",
|
||||||
|
method:"DELETE",
|
||||||
|
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
|
||||||
|
})
|
||||||
|
.done(function(){
|
||||||
|
addMsg('{% trans "button successfully deleted "%}','success');
|
||||||
|
$("#buttons_table").load("{% url 'note:template_list' %} #buttons_table");
|
||||||
|
})
|
||||||
|
.fail(function(){
|
||||||
|
addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' )
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
107
templates/treasury/invoice_form.html
Normal file
107
templates/treasury/invoice_form.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load crispy_forms_tags pretty_money %}
|
||||||
|
{% block content %}
|
||||||
|
<p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p>
|
||||||
|
<form method="post" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
{# Render the invoice form #}
|
||||||
|
{% crispy form %}
|
||||||
|
{# The next part concerns the product formset #}
|
||||||
|
{# Generate some hidden fields that manage the number of products, and make easier the parsing #}
|
||||||
|
{{ formset.management_form }}
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
{# Fill initial data #}
|
||||||
|
{% for form in formset %}
|
||||||
|
{% if forloop.first %}
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ form.designation.label }}<span class="asteriskField">*</span></th>
|
||||||
|
<th>{{ form.quantity.label }}<span class="asteriskField">*</span></th>
|
||||||
|
<th>{{ form.amount.label }}<span class="asteriskField">*</span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="form_body">
|
||||||
|
{% endif %}
|
||||||
|
<tr class="row-formset">
|
||||||
|
<td>{{ form.designation }}</td>
|
||||||
|
<td>{{ form.quantity }} </td>
|
||||||
|
<td>
|
||||||
|
{# Use custom input for amount, with the € symbol #}
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" name="product_set-{{ forloop.counter0 }}-amount" step="0.01"
|
||||||
|
id="id_product_set-{{ forloop.counter0 }}-amount"
|
||||||
|
value="{{ form.instance.amount|cents_to_euros }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">€</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
|
||||||
|
{{ form.invoice }}
|
||||||
|
{{ form.id }}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{# Display buttons to add and remove products #}
|
||||||
|
<div class="btn-group btn-block" role="group">
|
||||||
|
<button type="button" id="add_more" class="btn btn-primary">{% trans "Add product" %}</button>
|
||||||
|
<button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove product" %}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-block">
|
||||||
|
<button type="submit" class="btn btn-block btn-primary">{% trans "Submit" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="empty_form" style="display: none;">
|
||||||
|
{# Hidden div that store an empty product form, to be copied into new forms #}
|
||||||
|
<table class='no_error'>
|
||||||
|
<tbody id="for_real">
|
||||||
|
<tr class="row-formset">
|
||||||
|
<td>{{ formset.empty_form.designation }}</td>
|
||||||
|
<td>{{ formset.empty_form.quantity }} </td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" name="product_set-__prefix__-amount" step="0.01"
|
||||||
|
id="id_product_set-__prefix__-amount">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">€</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ formset.empty_form.invoice }}
|
||||||
|
{{ formset.empty_form.id }}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
{# Script that handles add and remove lines #}
|
||||||
|
IDS = {};
|
||||||
|
|
||||||
|
$("#id_product_set-TOTAL_FORMS").val($(".row-formset").length - 1);
|
||||||
|
|
||||||
|
$('#add_more').click(function () {
|
||||||
|
var form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
|
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
|
||||||
|
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
||||||
|
$('#id_product_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#remove_one').click(function () {
|
||||||
|
let form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
|
if (form_idx > 0) {
|
||||||
|
IDS[parseInt(form_idx) - 1] = $('#id_product_set-' + (parseInt(form_idx) - 1) + '-id').val();
|
||||||
|
$('#form_body tr:last-child').remove();
|
||||||
|
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) - 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
23
templates/treasury/invoice_list.html
Normal file
23
templates/treasury/invoice_list.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-12">
|
||||||
|
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary active">
|
||||||
|
{% trans "Invoice" %}s
|
||||||
|
</a>
|
||||||
|
<a href="{% url "treasury:remittance_list" %}" class="btn btn-sm btn-outline-primary">
|
||||||
|
{% trans "Remittance" %}s
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% render_table table %}
|
||||||
|
|
||||||
|
<a class="btn btn-primary" href="{% url 'treasury:invoice_create' %}">{% trans "New invoice" %}</a>
|
||||||
|
|
||||||
|
{% endblock %}
|
186
templates/treasury/invoice_sample.tex
Normal file
186
templates/treasury/invoice_sample.tex
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
\nonstopmode
|
||||||
|
\documentclass[11pt]{article}
|
||||||
|
|
||||||
|
\usepackage[french]{babel}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[a4paper]{geometry}
|
||||||
|
\usepackage{units}
|
||||||
|
\usepackage{bera}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{fancyhdr}
|
||||||
|
\usepackage{fp}
|
||||||
|
\usepackage{transparent}
|
||||||
|
\usepackage{eso-pic}
|
||||||
|
|
||||||
|
\def\TVA{0} % Taux de la TVA
|
||||||
|
|
||||||
|
\def\TotalHT{0}
|
||||||
|
\def\TotalTVA{0}
|
||||||
|
|
||||||
|
\newcommand{\AjouterProduit}[4]{% Arguments : Désignation, quantité, prix unitaire HT, prix total HT
|
||||||
|
\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}
|
||||||
|
\FPadd{\TotalTTC}{\TotalHT}{\TotalTVA}
|
||||||
|
\FPround{\TotalHT}{\TotalHT}{2}
|
||||||
|
\FPround{\TotalTVA}{\TotalTVA}{2}
|
||||||
|
\FPround{\TotalTTC}{\TotalTTC}{2}
|
||||||
|
\global\let\TotalHT\TotalHT
|
||||||
|
\global\let\TotalTVA\TotalTVA
|
||||||
|
\global\let\TotalTTC\TotalTTC
|
||||||
|
|
||||||
|
\cr \hline
|
||||||
|
Total HT & & & \TotalHT \cr
|
||||||
|
TVA \TVA~\% & & & \TotalTVA \cr
|
||||||
|
\hline \hline
|
||||||
|
\textbf{Total TTC} & & & \TotalTTC
|
||||||
|
}
|
||||||
|
|
||||||
|
\newcommand*\eaddto[2]{% version développée de \addto
|
||||||
|
\edef\tmp{#2}%
|
||||||
|
\expandafter\addto
|
||||||
|
\expandafter#1%
|
||||||
|
\expandafter{\tmp}%
|
||||||
|
}
|
||||||
|
|
||||||
|
\newcommand {\ListeProduits}{}
|
||||||
|
|
||||||
|
% Logo du BDE
|
||||||
|
\AddToShipoutPicture*{
|
||||||
|
\put(0,0){
|
||||||
|
\parbox[b][\paperheight]{\paperwidth}{%
|
||||||
|
\vfill
|
||||||
|
\centering
|
||||||
|
{\transparent{0.1}\includegraphics[width=\textwidth]{../../static/img/{{ obj.bde }}}}%
|
||||||
|
\vfill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%% A MODIFIER DANS LA FACTURE %%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% Infos Association
|
||||||
|
\def\MonNom{{"{"}}{{ obj.my_name }}} % Nom de l'association
|
||||||
|
\def\MonAdresseRue{{"{"}}{{ obj.my_address_street }}} % Adresse de l'association
|
||||||
|
\def\MonAdresseVille{{"{"}}{{ obj.my_city }}}
|
||||||
|
|
||||||
|
% Informations bancaires de l'association
|
||||||
|
\def\CodeBanque{{"{"}}{{ obj.bank_code|stringformat:".05d" }}}
|
||||||
|
\def\CodeGuichet{{"{"}}{{ obj.desk_code|stringformat:".05d" }}}
|
||||||
|
\def\NCompte{{"{"}}{{ obj.account_number|stringformat:".011d" }}}
|
||||||
|
\def\CleRib{{"{"}}{{ obj.rib_key|stringformat:".02d" }}}
|
||||||
|
\def\IBAN{FR76\CodeBanque\CodeGuichet\NCompte\CleRib}
|
||||||
|
\def\CodeBic{{"{"}}{{ obj.bic }}}
|
||||||
|
|
||||||
|
\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\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture
|
||||||
|
\def\FactureObjet {{"{"}}{{ obj.object|safe }} } % Objet du document
|
||||||
|
% Description de la facture
|
||||||
|
\def\FactureDescr {{"{"}}{{ obj.description|safe }}}
|
||||||
|
|
||||||
|
% Infos Client
|
||||||
|
\def\ClientNom{{"{"}}{{obj.name|safe}}} % Nom du client
|
||||||
|
\def\ClientAdresse{{"{"}}{{ obj.address|safe }}} % 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}}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
\geometry{verbose,tmargin=4em,bmargin=8em,lmargin=6em,rmargin=6em}
|
||||||
|
\setlength{\parindent}{1pt}
|
||||||
|
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
|
||||||
|
|
||||||
|
\thispagestyle{fancy}
|
||||||
|
\pagestyle{fancy}
|
||||||
|
\setlength{\parindent}{0pt}
|
||||||
|
|
||||||
|
\renewcommand{\headrulewidth}{0pt}
|
||||||
|
\cfoot{
|
||||||
|
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline
|
||||||
|
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
% Logo de la société
|
||||||
|
% \includegraphics{logo.jpg}
|
||||||
|
|
||||||
|
% Nom et adresse de la société
|
||||||
|
\MonNom \\
|
||||||
|
\MonAdresseRue \\
|
||||||
|
\MonAdresseVille
|
||||||
|
|
||||||
|
Facture n°\FactureNum
|
||||||
|
|
||||||
|
|
||||||
|
{\addtolength{\leftskip}{10.5cm} %in ERT
|
||||||
|
\ClientNom \\
|
||||||
|
\ClientAdresse \\
|
||||||
|
|
||||||
|
} %in ERT
|
||||||
|
|
||||||
|
|
||||||
|
\hspace*{10.5cm}
|
||||||
|
\FactureLieu, le \FactureDate
|
||||||
|
|
||||||
|
~\\~\\
|
||||||
|
|
||||||
|
\textbf{Objet : \FactureObjet \\}
|
||||||
|
|
||||||
|
\textnormal{\FactureDescr}
|
||||||
|
|
||||||
|
~\\
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{tabular}{lrrr}
|
||||||
|
\textbf{Désignation ~~~~~~} & \textbf{Prix unitaire} & \textbf{Quantité} & \textbf{Montant (EUR)} \\
|
||||||
|
\hline
|
||||||
|
\AfficheResultat{}
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
~\\
|
||||||
|
|
||||||
|
\ifthenelse{\equal{\FactureAcquittee}{oui}}{
|
||||||
|
Facture acquittée.
|
||||||
|
}{
|
||||||
|
|
||||||
|
À régler par chèque ou par virement bancaire :
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{tabular}{|c c c c|}
|
||||||
|
\hline
|
||||||
|
\textbf{Code banque} & \textbf{Code guichet} & \textbf{N° de Compte} & \textbf{Clé RIB}\\
|
||||||
|
\CodeBanque & \CodeGuichet & \NCompte & \CleRib \\
|
||||||
|
\hline
|
||||||
|
\textbf{IBAN N°} & \multicolumn{3}{|l|} \IBAN \\
|
||||||
|
\hline
|
||||||
|
\textbf{Code BIC} & \multicolumn{3}{|l|}\CodeBic \\
|
||||||
|
\hline
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
TVA non applicable, article 293 B du CGI.
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\end{document}
|
37
templates/treasury/remittance_form.html
Normal file
37
templates/treasury/remittance_form.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load crispy_forms_tags pretty_money %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% trans "Remittance #" %}{{ object.pk }}</h1>
|
||||||
|
|
||||||
|
<p><a class="btn btn-default" href="{% url 'treasury:remittance_list' %}">{% trans "Remittances list" %}</a></p>
|
||||||
|
|
||||||
|
{% if object.pk %}
|
||||||
|
<div id="div_id_type" class="form-group"><label for="id_count" class="col-form-label">{% trans "Count" %}</label>
|
||||||
|
<div class="">
|
||||||
|
<input type="text" name="count" value="{{ object.count }}" class="textinput textInput form-control" id="id_count" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div_id_type" class="form-group"><label for="id_amount" class="col-form-label">{% trans "Amount" %}</label>
|
||||||
|
<div class="">
|
||||||
|
<input class="textinput textInput form-control" type="text" value="{{ object.amount|pretty_money }}" id="id_amount" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% crispy form %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>{% trans "Linked transactions" %}</h2>
|
||||||
|
{% if special_transactions.data %}
|
||||||
|
{% render_table special_transactions %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "There is no transaction linked with this remittance." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
56
templates/treasury/remittance_list.html
Normal file
56
templates/treasury/remittance_list.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-12">
|
||||||
|
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
|
||||||
|
<a href="{% url "treasury:invoice_list" %}" class="btn btn-sm btn-outline-primary">
|
||||||
|
{% trans "Invoice" %}s
|
||||||
|
</a>
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary active">
|
||||||
|
{% trans "Remittance" %}s
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{% trans "Opened remittances" %}</h2>
|
||||||
|
{% if opened_remittances.data %}
|
||||||
|
{% render_table opened_remittances %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "There is no opened remittance." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a class="btn btn-primary" href="{% url 'treasury:remittance_create' %}">{% trans "New remittance" %}</a>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>{% trans "Transfers without remittances" %}</h2>
|
||||||
|
{% if special_transactions_no_remittance.data %}
|
||||||
|
{% render_table special_transactions_no_remittance %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "There is no transaction without any linked remittance." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>{% trans "Transfers with opened remittances" %}</h2>
|
||||||
|
{% if special_transactions_with_remittance.data %}
|
||||||
|
{% render_table special_transactions_with_remittance %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "There is no transaction with an opened linked remittance." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>{% trans "Closed remittances" %}</h2>
|
||||||
|
{% render_table closed_remittances %}
|
||||||
|
{% endblock %}
|
9
templates/treasury/specialtransactionproxy_form.html
Normal file
9
templates/treasury/specialtransactionproxy_form.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load crispy_forms_tags pretty_money %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% block content %}
|
||||||
|
<p><a class="btn btn-default" href="{% url 'treasury:remittance_list' %}">{% trans "Remittances list" %}</a></p>
|
||||||
|
{% crispy form %}
|
||||||
|
{% endblock %}
|
2
tox.ini
2
tox.ini
@ -30,7 +30,7 @@ deps =
|
|||||||
pep8-naming
|
pep8-naming
|
||||||
pyflakes
|
pyflakes
|
||||||
commands =
|
commands =
|
||||||
flake8 apps/activity apps/api apps/logs apps/member apps/note
|
flake8 apps/activity apps/api apps/logs apps/member apps/note apps/permission apps/treasury
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# Ignore too many errors, should be reduced in the future
|
# Ignore too many errors, should be reduced in the future
|
||||||
|
Loading…
Reference in New Issue
Block a user