Merge branch 'tranfer_front' into 'master'

Transfer front

See merge request bde/nk20!76
This commit is contained in:
ynerant 2020-05-07 18:54:51 +02:00
commit 0776ed416c
21 changed files with 701 additions and 454 deletions

View File

@ -3,6 +3,7 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import F, Q from django.db.models import F, Q
@ -138,8 +139,14 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
| Q(note__noteuser__user__last_name__regex=pattern) | Q(note__noteuser__user__last_name__regex=pattern)
| Q(name__regex=pattern) | Q(name__regex=pattern)
| Q(normalized_name__regex=Alias.normalize(pattern)))) \ | Q(normalized_name__regex=Alias.normalize(pattern)))) \
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))\ .filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
.distinct()[:20] if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql_psycopg2':
note_qs = note_qs.distinct('note__pk')[:20]
else:
# SQLite doesn't support distinct fields. For compatibility reason (in dev mode), the note list will only
# have distinct aliases rather than distinct notes with a SQLite DB, but it can fill the result page.
# In production mode, please use PostgreSQL.
note_qs = note_qs.distinct()[:20]
for note in note_qs: for note in note_qs:
note.type = "Adhérent" note.type = "Adhérent"
note.activity = activity note.activity = activity

View File

@ -3,6 +3,8 @@
from rest_framework import serializers from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer from rest_polymorphic.serializers import PolymorphicSerializer
from note_kfet.middlewares import get_current_authenticated_user
from permission.backends import PermissionBackend
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
@ -97,6 +99,35 @@ class NotePolymorphicSerializer(PolymorphicSerializer):
model = Note model = Note
class ConsumerSerializer(serializers.ModelSerializer):
"""
REST API Nested Serializer for Consumers.
return Alias, and the note Associated to it in
"""
note = serializers.SerializerMethodField()
email_confirmed = serializers.SerializerMethodField()
class Meta:
model = Alias
fields = '__all__'
def get_note(self, obj):
"""
Display information about the associated note
"""
# If the user has no right to see the note, then we only display the note identifier
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note):
print(obj.pk)
return NotePolymorphicSerializer().to_representation(obj.note)
return dict(id=obj.id)
def get_email_confirmed(self, obj):
if isinstance(obj.note, NoteUser):
return obj.note.user.profile.email_confirmed
return True
class TemplateCategorySerializer(serializers.ModelSerializer): class TemplateCategorySerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Transaction templates. REST API Serializer for Transaction templates.

View File

@ -1,7 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, \ from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet
@ -11,6 +11,7 @@ def register_note_urls(router, path):
""" """
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -10,8 +10,8 @@ from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TransactionTemplateSerializer, TransactionPolymorphicSerializer TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
from ..models.notes import Note, Alias from ..models.notes import Note, Alias
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
@ -90,6 +90,30 @@ class AliasViewSet(ReadProtectedModelViewSet):
return queryset return queryset
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
queryset = Alias.objects.all()
serializer_class = ConsumerSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
ordering_fields = ['name', 'normalized_name']
def get_queryset(self):
"""
Parse query and apply filters.
:return: The filtered set of requested aliases
"""
queryset = super().get_queryset()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(
Q(name__regex="^" + alias)
| Q(normalized_name__regex="^" + Alias.normalize(alias))
| Q(normalized_name__regex="^" + alias.lower()))
return queryset
class TemplateCategoryViewSet(ReadProtectedModelViewSet): class TemplateCategoryViewSet(ReadProtectedModelViewSet):
""" """
REST API View set. REST API View set.

View File

@ -4,7 +4,7 @@
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete from note_kfet.inputs import Autocomplete, AmountInput
from .models import TransactionTemplate, NoteClub from .models import TransactionTemplate, NoteClub
@ -24,11 +24,6 @@ class TransactionTemplateForm(forms.ModelForm):
model = TransactionTemplate model = TransactionTemplate
fields = '__all__' fields = '__all__'
# Le champ de destination est remplacé par un champ d'auto-complétion.
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
# et récupère les aliases valides
# Pour force le type d'une note, il faut rajouter le paramètre :
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
widgets = { widgets = {
'destination': 'destination':
Autocomplete( Autocomplete(
@ -41,4 +36,5 @@ class TransactionTemplateForm(forms.ModelForm):
'placeholder': 'Note ...', 'placeholder': 'Note ...',
}, },
), ),
'amount': AmountInput(),
} }

View File

@ -227,8 +227,7 @@ class RecurrentTransaction(Transaction):
template = models.ForeignKey( template = models.ForeignKey(
TransactionTemplate, TransactionTemplate,
null=True, on_delete=models.PROTECT,
on_delete=models.SET_NULL,
) )
category = models.ForeignKey( category = models.ForeignKey(
TemplateCategory, TemplateCategory,

View File

@ -55,7 +55,7 @@ class HistoryTable(tables.Table):
"class": lambda record: str(record.valid).lower() + ' validate', "class": lambda record: str(record.valid).lower() + ' validate',
"data-toggle": "tooltip", "data-toggle": "tooltip",
"title": lambda record: _("Click to invalidate") if record.valid else _("Click to validate"), "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() + ')', "onclick": lambda record: 'de_validate(' + str(record.id) + ', ' + str(record.valid).lower() + ')',
"onmouseover": lambda record: '$("#invalidity_reason_' "onmouseover": lambda record: '$("#invalidity_reason_'
+ str(record.id) + '").show();$("#invalidity_reason_' + str(record.id) + '").show();$("#invalidity_reason_'
+ str(record.id) + '").focus();', + str(record.id) + '").focus();',
@ -129,13 +129,14 @@ class ButtonTable(tables.Table):
'table table-bordered condensed table-hover' 'table table-bordered condensed table-hover'
} }
row_attrs = { row_attrs = {
'class': lambda record: 'table-row ' + 'table-success' if record.display else 'table-danger', 'class': lambda record: 'table-row ' + ('table-success' if record.display else 'table-danger'),
'id': lambda record: "row-" + str(record.pk), 'id': lambda record: "row-" + str(record.pk),
'data-href': lambda record: record.pk 'data-href': lambda record: record.pk
} }
model = TransactionTemplate model = TransactionTemplate
exclude = ('id',) exclude = ('id',)
order_by = ('type', '-display', 'destination__name', 'name',)
edit = tables.LinkColumn('note:template_update', edit = tables.LinkColumn('note:template_update',
args=[A('pk')], args=[A('pk')],

View File

@ -1,5 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -80,6 +81,33 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
form_class = TransactionTemplateForm form_class = TransactionTemplateForm
success_url = reverse_lazy('note:template_list') success_url = reverse_lazy('note:template_list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "logs" in settings.INSTALLED_APPS:
from logs.models import Changelog
update_logs = Changelog.objects.filter(
model=ContentType.objects.get_for_model(TransactionTemplate),
instance_pk=self.object.pk,
action="edit",
)
price_history = []
for log in update_logs.all():
old_dict = json.loads(log.previous)
new_dict = json.loads(log.data)
old_price = old_dict["amount"]
new_price = new_dict["amount"]
if old_price != new_price:
price_history.append(dict(price=old_price, time=log.timestamp))
price_history.append(dict(price=self.object.amount, time=None))
price_history.reverse()
context["price_history"] = price_history
return context
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """

View File

@ -8,6 +8,7 @@ from django.contrib.auth.models import User, AnonymousUser
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q, F from django.db.models import Q, F
from note.models import Note, NoteUser, NoteClub, NoteSpecial from note.models import Note, NoteUser, NoteClub, NoteSpecial
from note_kfet import settings
from note_kfet.middlewares import get_current_session from note_kfet.middlewares import get_current_session
from member.models import Membership, Club from member.models import Membership, Club
@ -107,7 +108,7 @@ class PermissionBackend(ModelBackend):
# Anonymous users can't do anything # Anonymous users can't do anything
return Q(pk=-1) return Q(pk=-1)
if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: if user.is_superuser and get_current_session().get("permission_mask", 42) >= 42:
# Superusers have all rights # Superusers have all rights
return Q() return Q()
@ -141,9 +142,9 @@ class PermissionBackend(ModelBackend):
sess = get_current_session() sess = get_current_session()
if sess is not None and sess.session_key is None: if sess is not None and sess.session_key is None:
return Permission.objects.none() return False
if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42: if user_obj.is_superuser and get_current_session().get("permission_mask", 42) >= 42:
return True return True
if obj is None: if obj is None:

View File

@ -1,6 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from note.models import NoteSpecial
from treasury.models import SpecialTransactionProxy, RemittanceType from treasury.models import SpecialTransactionProxy, RemittanceType
@ -8,5 +9,6 @@ def save_special_transaction(instance, created, **kwargs):
""" """
When a special transaction is created, we create its linked proxy When a special transaction is created, we create its linked proxy
""" """
if created and RemittanceType.objects.filter(note=instance.source).exists(): if created and isinstance(instance.source, NoteSpecial) \
and RemittanceType.objects.filter(note=instance.source).exists():
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save() SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()

View File

@ -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-04-26 00:45+0200\n" "POT-Creation-Date: 2020-04-27 03:55+0200\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"
@ -205,7 +205,7 @@ msgstr ""
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: apps/activity/views.py:45 templates/base.html:106 #: apps/activity/views.py:46 templates/base.html:121
msgid "Activities" msgid "Activities"
msgstr "" msgstr ""
@ -245,7 +245,7 @@ msgstr ""
msgid "create" msgid "create"
msgstr "" msgstr ""
#: apps/logs/models.py:61 apps/note/tables.py:144 #: apps/logs/models.py:61 apps/note/tables.py:145
#: templates/activity/activity_detail.html:67 #: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "" msgstr ""
@ -751,7 +751,7 @@ msgstr ""
#: apps/note/models/transactions.py:216 #: apps/note/models/transactions.py:216
#: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/activity/activity_entry.html:13 templates/base.html:84
#: templates/note/transaction_form.html:19 #: templates/note/transaction_form.html:19
#: templates/note/transaction_form.html:145 #: templates/note/transaction_form.html:140
msgid "Transfer" msgid "Transfer"
msgstr "" msgstr ""
@ -810,11 +810,11 @@ msgstr ""
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: apps/note/views.py:40 #: apps/note/views.py:41
msgid "Transfer money" msgid "Transfer money"
msgstr "" msgstr ""
#: apps/note/views.py:109 templates/base.html:79 #: apps/note/views.py:137 templates/base.html:94
msgid "Consumptions" msgid "Consumptions"
msgstr "" msgstr ""
@ -944,7 +944,7 @@ msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
#: apps/treasury/apps.py:12 templates/base.html:111 #: apps/treasury/apps.py:12 templates/base.html:126
msgid "Treasury" msgid "Treasury"
msgstr "" msgstr ""
@ -1509,15 +1509,15 @@ msgstr ""
msgid "The ENS Paris-Saclay BDE note." msgid "The ENS Paris-Saclay BDE note."
msgstr "" msgstr ""
#: templates/base.html:89 #: templates/base.html:104
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: templates/base.html:94 #: templates/base.html:109
msgid "Clubs" msgid "Clubs"
msgstr "" msgstr ""
#: templates/base.html:100 #: templates/base.html:115
msgid "Registrations" msgid "Registrations"
msgstr "" msgstr ""
@ -1655,31 +1655,36 @@ msgstr ""
msgid "There is no user with this pattern." msgid "There is no user with this pattern."
msgstr "" msgstr ""
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:55 #: templates/note/conso_form.html:28
msgid "Select emitters" msgid "Consum"
msgstr "" msgstr ""
#: templates/note/conso_form.html:45 #: templates/note/conso_form.html:39 templates/note/transaction_form.html:61
#: templates/note/transaction_form.html:76
msgid "Name or alias..."
msgstr ""
#: templates/note/conso_form.html:48
msgid "Select consumptions" msgid "Select consumptions"
msgstr "" msgstr ""
#: templates/note/conso_form.html:51 #: templates/note/conso_form.html:57
msgid "Consume!" msgid "Consume!"
msgstr "" msgstr ""
#: templates/note/conso_form.html:64 #: templates/note/conso_form.html:71
msgid "Most used buttons" msgid "Most used buttons"
msgstr "" msgstr ""
#: templates/note/conso_form.html:126 #: templates/note/conso_form.html:134
msgid "Single consumptions" msgid "Single consumptions"
msgstr "" msgstr ""
#: templates/note/conso_form.html:130 #: templates/note/conso_form.html:139
msgid "Double consumptions" msgid "Double consumptions"
msgstr "" msgstr ""
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 #: templates/note/conso_form.html:150 templates/note/transaction_form.html:151
msgid "Recent transactions history" msgid "Recent transactions history"
msgstr "" msgstr ""
@ -1687,53 +1692,67 @@ msgstr ""
msgid "Gift" msgid "Gift"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:73 #: templates/note/transaction_form.html:55
msgid "External payment" msgid "Select emitters"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:81 #: templates/note/transaction_form.html:70
msgid "Transfer type"
msgstr ""
#: templates/note/transaction_form.html:116
#: templates/note/transaction_form.html:169
#: templates/note/transaction_form.html:176
msgid "Select receivers" msgid "Select receivers"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:138 #: templates/note/transaction_form.html:87
msgid "Action"
msgstr ""
#: templates/note/transaction_form.html:102
msgid "Reason" msgid "Reason"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:183 #: templates/note/transaction_form.html:110
msgid "Credit note" msgid "Transfer type"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:190 #: templates/note/transactiontemplate_form.html:10
msgid "Debit note"
msgstr ""
#: templates/note/transactiontemplate_form.html:6
msgid "Buttons list" msgid "Buttons list"
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:9 #: templates/note/transactiontemplate_form.html:21
msgid "search button" msgid "Price history"
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:13 #: templates/note/transactiontemplate_form.html:24
msgid "Obsolete since"
msgstr ""
#: templates/note/transactiontemplate_form.html:24
msgid "Current price"
msgstr ""
#: templates/note/transactiontemplate_list.html:9
msgid "Search button"
msgstr ""
#: templates/note/transactiontemplate_list.html:11
msgid "Name of the button..."
msgstr ""
#: templates/note/transactiontemplate_list.html:16
msgid "Display visible buttons only"
msgstr ""
#: templates/note/transactiontemplate_list.html:21
msgid "New button" msgid "New button"
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:20 #: templates/note/transactiontemplate_list.html:28
msgid "buttons listing " msgid "buttons listing "
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:70 #: templates/note/transactiontemplate_list.html:86
msgid "button successfully deleted " msgid "button successfully deleted "
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:74 #: templates/note/transactiontemplate_list.html:90
msgid "Unable to delete button " msgid "Unable to delete button "
msgstr "" msgstr ""

View File

@ -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-04-26 00:45+0200\n" "POT-Creation-Date: 2020-04-27 03:55+0200\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"
@ -206,7 +206,7 @@ msgstr "Note"
msgid "Balance" msgid "Balance"
msgstr "Solde du compte" msgstr "Solde du compte"
#: apps/activity/views.py:45 templates/base.html:106 #: apps/activity/views.py:46 templates/base.html:121
msgid "Activities" msgid "Activities"
msgstr "Activités" msgstr "Activités"
@ -246,7 +246,7 @@ msgstr "Nouvelles données"
msgid "create" msgid "create"
msgstr "Créer" msgstr "Créer"
#: apps/logs/models.py:61 apps/note/tables.py:144 #: apps/logs/models.py:61 apps/note/tables.py:145
#: templates/activity/activity_detail.html:67 #: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "Modifier" msgstr "Modifier"
@ -759,7 +759,7 @@ msgstr "transactions"
#: apps/note/models/transactions.py:216 #: apps/note/models/transactions.py:216
#: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/activity/activity_entry.html:13 templates/base.html:84
#: templates/note/transaction_form.html:19 #: templates/note/transaction_form.html:19
#: templates/note/transaction_form.html:145 #: templates/note/transaction_form.html:140
msgid "Transfer" msgid "Transfer"
msgstr "Virement" msgstr "Virement"
@ -818,11 +818,11 @@ msgstr "Supprimer"
msgid "Edit" msgid "Edit"
msgstr "Éditer" msgstr "Éditer"
#: apps/note/views.py:40 #: apps/note/views.py:41
msgid "Transfer money" msgid "Transfer money"
msgstr "Transférer de l'argent" msgstr "Transférer de l'argent"
#: apps/note/views.py:109 templates/base.html:79 #: apps/note/views.py:137 templates/base.html:94
msgid "Consumptions" msgid "Consumptions"
msgstr "Consommations" msgstr "Consommations"
@ -965,7 +965,7 @@ msgstr ""
"Le montant crédité est trop faible pour adhérer, il doit être au minimum de " "Le montant crédité est trop faible pour adhérer, il doit être au minimum de "
"{}" "{}"
#: apps/treasury/apps.py:12 templates/base.html:111 #: apps/treasury/apps.py:12 templates/base.html:126
msgid "Treasury" msgid "Treasury"
msgstr "Trésorerie" msgstr "Trésorerie"
@ -1558,15 +1558,15 @@ msgstr "Toutes les activités"
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:89 #: templates/base.html:104
msgid "Users" msgid "Users"
msgstr "Utilisateurs" msgstr "Utilisateurs"
#: templates/base.html:94 #: templates/base.html:109
msgid "Clubs" msgid "Clubs"
msgstr "Clubs" msgstr "Clubs"
#: templates/base.html:100 #: templates/base.html:115
msgid "Registrations" msgid "Registrations"
msgstr "Inscriptions" msgstr "Inscriptions"
@ -1709,31 +1709,36 @@ msgstr "Sauvegarder les changements"
msgid "There is no user with this pattern." msgid "There is no user with this pattern."
msgstr "Il n'y a pas d'utilisateur trouvé avec cette entrée." msgstr "Il n'y a pas d'utilisateur trouvé avec cette entrée."
#: templates/note/conso_form.html:28 templates/note/transaction_form.html:55 #: templates/note/conso_form.html:28
msgid "Select emitters" msgid "Consum"
msgstr "Sélection des émetteurs" msgstr "Consommer"
#: templates/note/conso_form.html:45 #: templates/note/conso_form.html:39 templates/note/transaction_form.html:61
#: templates/note/transaction_form.html:76
msgid "Name or alias..."
msgstr ""
#: templates/note/conso_form.html:48
msgid "Select consumptions" msgid "Select consumptions"
msgstr "Sélection des consommations" msgstr "Sélection des consommations"
#: templates/note/conso_form.html:51 #: templates/note/conso_form.html:57
msgid "Consume!" msgid "Consume!"
msgstr "Consommer !" msgstr "Consommer !"
#: templates/note/conso_form.html:64 #: templates/note/conso_form.html:71
msgid "Most used buttons" msgid "Most used buttons"
msgstr "Boutons les plus utilisés" msgstr "Boutons les plus utilisés"
#: templates/note/conso_form.html:126 #: templates/note/conso_form.html:134
msgid "Single consumptions" msgid "Single consumptions"
msgstr "Consommations simples" msgstr "Consommations simples"
#: templates/note/conso_form.html:130 #: templates/note/conso_form.html:139
msgid "Double consumptions" msgid "Double consumptions"
msgstr "Consommations doubles" msgstr "Consommations doubles"
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 #: templates/note/conso_form.html:150 templates/note/transaction_form.html:151
msgid "Recent transactions history" msgid "Recent transactions history"
msgstr "Historique des transactions récentes" msgstr "Historique des transactions récentes"
@ -1741,53 +1746,67 @@ msgstr "Historique des transactions récentes"
msgid "Gift" msgid "Gift"
msgstr "Don" msgstr "Don"
#: templates/note/transaction_form.html:73 #: templates/note/transaction_form.html:55
msgid "External payment" msgid "Select emitters"
msgstr "Paiement externe" msgstr "Sélection des émetteurs"
#: templates/note/transaction_form.html:81 #: templates/note/transaction_form.html:70
msgid "Transfer type"
msgstr "Type de transfert"
#: templates/note/transaction_form.html:116
#: templates/note/transaction_form.html:169
#: 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:138 #: templates/note/transaction_form.html:87
msgid "Action"
msgstr "Action"
#: templates/note/transaction_form.html:102
msgid "Reason" msgid "Reason"
msgstr "Raison" msgstr "Raison"
#: templates/note/transaction_form.html:183 #: templates/note/transaction_form.html:110
msgid "Credit note" msgid "Transfer type"
msgstr "Note à recharger" msgstr "Type de transfert"
#: templates/note/transaction_form.html:190 #: templates/note/transactiontemplate_form.html:10
msgid "Debit note"
msgstr "Note à débiter"
#: templates/note/transactiontemplate_form.html:6
msgid "Buttons list" msgid "Buttons list"
msgstr "Liste des boutons" msgstr "Liste des boutons"
#: templates/note/transactiontemplate_form.html:21
msgid "Price history"
msgstr "Historique des prix"
#: templates/note/transactiontemplate_form.html:24
msgid "Obsolete since"
msgstr "Obsolète depuis"
#: templates/note/transactiontemplate_form.html:24
msgid "Current price"
msgstr "Prix actuel"
#: templates/note/transactiontemplate_list.html:9 #: templates/note/transactiontemplate_list.html:9
msgid "search button" msgid "Search button"
msgstr "Chercher un bouton" msgstr "Chercher un bouton"
#: templates/note/transactiontemplate_list.html:13 #: templates/note/transactiontemplate_list.html:11
msgid "Name of the button..."
msgstr "Nom du bouton ..."
#: templates/note/transactiontemplate_list.html:16
msgid "Display visible buttons only"
msgstr "N'afficher que les boutons visibles uniquement"
#: templates/note/transactiontemplate_list.html:21
msgid "New button" msgid "New button"
msgstr "Nouveau bouton" msgstr "Nouveau bouton"
#: templates/note/transactiontemplate_list.html:20 #: templates/note/transactiontemplate_list.html:28
msgid "buttons listing " msgid "buttons listing "
msgstr "Liste des boutons" msgstr "Liste des boutons"
#: templates/note/transactiontemplate_list.html:70 #: templates/note/transactiontemplate_list.html:86
msgid "button successfully deleted " msgid "button successfully deleted "
msgstr "Le bouton a bien été supprimé" msgstr "Le bouton a bien été supprimé"
#: templates/note/transactiontemplate_list.html:74 #: templates/note/transactiontemplate_list.html:90
msgid "Unable to delete button " msgid "Unable to delete button "
msgstr "Impossible de supprimer le bouton " msgstr "Impossible de supprimer le bouton "

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -79,17 +79,36 @@ function refreshBalance() {
* @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
*/ */
function getMatchedNotes(pattern, fun) { function getMatchedNotes(pattern, fun) {
$.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club|activity&ordering=normalized_name", fun); $.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club&ordering=normalized_name", fun);
} }
/** /**
* Generate a <li> entry with a given id and text * Generate a <li> entry with a given id and text
*/ */
function li(id, text) { function li(id, text, extra_css) {
return "<li class=\"list-group-item py-1 d-flex justify-content-between align-items-center\"" + return "<li class=\"list-group-item py-1 px-2 d-flex justify-content-between align-items-center text-truncate " + extra_css + "\"" +
" id=\"" + id + "\">" + text + "</li>\n"; " id=\"" + id + "\">" + text + "</li>\n";
} }
/**
* Return style to apply according to the balance of the note and the validation status of the email address
* @param note The concerned note.
*/
function displayStyle(note) {
let balance = note.balance;
var css = "";
if (balance < -5000)
css += " text-danger bg-dark";
else if (balance < -1000)
css += " text-danger";
else if (balance < 0)
css += " text-warning";
if (!note.email_confirmed)
css += " text-white bg-primary";
return css;
}
/** /**
* Render note name and picture * Render note name and picture
* @param note The note to render * @param note The note to render
@ -100,24 +119,26 @@ function li(id, text) {
function displayNote(note, alias, user_note_field = null, profile_pic_field = null) { function displayNote(note, alias, user_note_field = null, profile_pic_field = null) {
if (!note.display_image) { if (!note.display_image) {
note.display_image = '/media/pic/default.png'; note.display_image = '/media/pic/default.png';
$.getJSON("/api/note/note/" + note.id + "/?format=json", function(new_note) {
note.display_image = new_note.display_image.replace("http:", "https:");
note.name = new_note.name;
note.balance = new_note.balance;
note.user = new_note.user;
displayNote(note, alias, user_note_field, profile_pic_field);
});
return;
} }
let img = note.display_image; let img = note.display_image;
if (alias !== note.name) if (alias !== note.name && note.name)
alias += " (aka. " + note.name + ")"; alias += " (aka. " + note.name + ")";
if (user_note_field !== null) if (user_note_field !== null) {
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" : " + pretty_money(note.balance)))); $("#" + user_note_field).removeAttr('class');
if (profile_pic_field != null) $("#" + user_note_field).addClass(displayStyle(note));
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance))));
if (profile_pic_field != null) {
$("#" + profile_pic_field).attr('src', img); $("#" + profile_pic_field).attr('src', img);
$("#" + profile_pic_field).click(function () {
console.log(note);
if (note.resourcetype === "NoteUser") {
document.location.href = "/accounts/user/" + note.user;
} else if (note.resourcetype === "NoteClub") {
document.location.href = "/accounts/club/" + note.club;
}
});
}
}
} }
/** /**
@ -141,7 +162,8 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not
disp.quantity -= disp.id === d.id ? 1 : 0; disp.quantity -= disp.id === d.id ? 1 : 0;
new_notes_display.push(disp); new_notes_display.push(disp);
html += li(note_prefix + "_" + disp.id, disp.name html += li(note_prefix + "_" + disp.id, disp.name
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>"); + "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>",
displayStyle(disp.note));
} }
}); });
@ -165,7 +187,6 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not
/** /**
* Generate an auto-complete field to query a note with its alias * Generate an auto-complete field to query a note with its alias
* @param field_id The identifier of the text field where the alias is typed * @param field_id The identifier of the text field where the alias is typed
* @param alias_matched_id The div block identifier where the matched aliases are displayed
* @param note_list_id The div block identifier where the notes of the buyers are displayed * @param note_list_id The div block identifier where the notes of the buyers are displayed
* @param notes An array containing the note objects of the buyers * @param notes An array containing the note objects of the buyers
* @param notes_display An array containing the infos of the buyers: [alias, note id, note object, quantity] * @param notes_display An array containing the infos of the buyers: [alias, note id, note object, quantity]
@ -179,20 +200,31 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not
* the associated note is not displayed. * the associated note is not displayed.
* Useful for a consumption if the item is selected before. * Useful for a consumption if the item is selected before.
*/ */
function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes_display, alias_prefix="alias", function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_prefix = "alias",
note_prefix = "note", user_note_field = null, profile_pic_field = null, alias_click = null) { note_prefix = "note", user_note_field = null, profile_pic_field = null, alias_click = null) {
let field = $("#" + field_id); let field = $("#" + field_id);
// When the user clicks on the search field, it is immediately cleared
// Configure tooltip
field.tooltip({
html: true,
placement: 'bottom',
title: 'Loading...',
trigger: 'manual',
container: field.parent()
});
// Clear search on click
field.click(function () { field.click(function () {
field.tooltip('hide');
field.val(""); field.val("");
}); });
let old_pattern = null; let old_pattern = null;
// When the user type "Enter", the first alias is clicked, and the informations are displayed // When the user type "Enter", the first alias is clicked
field.keypress(function (event) { field.keypress(function (event) {
if (event.originalEvent.charCode === 13) { if (event.originalEvent.charCode === 13 && notes.length > 0) {
let li_obj = $("#" + alias_matched_id + " li").first(); let li_obj = field.parent().find("ul li").first();
displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field); displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field);
li_obj.trigger("click"); li_obj.trigger("click");
} }
@ -204,52 +236,51 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes
return; return;
let pattern = field.val(); let pattern = field.val();
// If the pattern is not modified, we don't query the API // If the pattern is not modified, we don't query the API
if (pattern === old_pattern || pattern === "") if (pattern === old_pattern)
return; return;
old_pattern = pattern; old_pattern = pattern;
// Clear old matched notes
notes.length = 0; notes.length = 0;
let aliases_matched_obj = $("#" + alias_matched_id); // get matched Alias with note associated
let aliases_matched_html = ""; if (pattern === "") {
field.tooltip('hide');
notes.length = 0;
return;
}
// Get matched notes with the given pattern $.getJSON("/api/note/consumer/?format=json&alias="
getMatchedNotes(pattern, function(aliases) { + pattern
+ "&search=user|club&ordering=normalized_name",
function (consumers) {
// The response arrived too late, we stop the request // The response arrived too late, we stop the request
if (pattern !== $("#" + field_id).val()) if (pattern !== $("#" + field_id).val())
return; return;
aliases.results.forEach(function (alias) { // Build tooltip content
let note = alias.note; let aliases_matched_html = '<ul class="list-group list-group-flush">';
note = { consumers.results.forEach(function (consumer) {
id: note, let note = consumer.note;
name: alias.name, note.email_confirmed = consumer.email_confirmed;
alias: alias, let extra_css = displayStyle(note);
balance: null aliases_matched_html += li(alias_prefix + '_' + consumer.id,
}; consumer.name,
aliases_matched_html += li(alias_prefix + "_" + alias.id, alias.name); extra_css);
notes.push(note); notes.push(note);
}); });
aliases_matched_html += '</ul>';
// Display the list of matched aliases // Show tooltip
aliases_matched_obj.html(aliases_matched_html); field.attr('data-original-title', aliases_matched_html).tooltip('show');
notes.forEach(function (note) { consumers.results.forEach(function (consumer) {
let alias = note.alias; let note = consumer.note;
let alias_obj = $("#" + alias_prefix + "_" + alias.id); let consumer_obj = $("#" + alias_prefix + "_" + consumer.id);
// When an alias is hovered, the profile picture and the balance are displayed at the right place consumer_obj.hover(function () {
alias_obj.hover(function () { displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field)
displayNote(note, alias.name, user_note_field, profile_pic_field);
}); });
consumer_obj.click(function () {
// When the user click on an alias, the associated note is added to the emitters
alias_obj.click(function () {
field.val("");
old_pattern = "";
// If the note is already an emitter, we increase the quantity
var disp = null; var disp = null;
notes_display.forEach(function (d) { notes_display.forEach(function (d) {
// We compare the note ids // We compare the note ids
@ -261,8 +292,8 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes
// In the other case, we add a new emitter // In the other case, we add a new emitter
if (disp == null) { if (disp == null) {
disp = { disp = {
name: alias.name, name: consumer.name,
id: note.id, id: consumer.id,
note: note, note: note,
quantity: 1 quantity: 1
}; };
@ -277,13 +308,19 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes
let note_list = $("#" + note_list_id); let note_list = $("#" + note_list_id);
let html = ""; let html = "";
notes_display.forEach(function (disp) { notes_display.forEach(function (disp) {
html += li(note_prefix + "_" + disp.id, disp.name html += li(note_prefix + "_" + disp.id,
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>"); disp.name
+ "<span class=\"badge badge-dark badge-pill\">"
+ disp.quantity + "</span>",
displayStyle(disp.note));
}); });
// Emitters are displayed // Emitters are displayed
note_list.html(html); note_list.html(html);
// Update tooltip position
field.tooltip('update');
notes_display.forEach(function (disp) { notes_display.forEach(function (disp) {
let line_obj = $("#" + note_prefix + "_" + disp.id); let line_obj = $("#" + note_prefix + "_" + disp.id);
// Hover an emitter display also the profile picture // Hover an emitter display also the profile picture
@ -295,27 +332,13 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes
line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field,
profile_pic_field)); profile_pic_field));
}); });
})
}); });
});
});
});
}
// When a validate button is clicked, we switch the validation status
function in_validate(id, validated) {
let invalidity_reason;
let reason_obj = $("#invalidity_reason_" + id);
if (validated)
invalidity_reason = reason_obj.val();
else
invalidity_reason = null;
$("#validate_" + id).html("<i class='fa fa-spinner'></i>"); $("#validate_" + id).html("<i class='fa fa-spinner'></i>");
// Perform a PATCH request to the API in order to update the transaction // Perform a PATCH request to the API in order to update the transaction
// If the user has insuffisent rights, an error message will appear // If the user has insufficient rights, an error message will appear
$.ajax({ $.ajax({
"url": "/api/note/transaction/transaction/" + id + "/", "url": "/api/note/transaction/transaction/" + id + "/",
type: "PATCH", type: "PATCH",
@ -324,13 +347,13 @@ function in_validate(id, validated) {
"X-CSRFTOKEN": CSRF_TOKEN "X-CSRFTOKEN": CSRF_TOKEN
}, },
data: { data: {
resourcetype: "RecurrentTransaction", "resourcetype": "RecurrentTransaction",
valid: !validated, "valid": !validated,
invalidity_reason: invalidity_reason, "invalidity_reason": invalidity_reason,
}, },
success: function () { success: function () {
// Refresh jQuery objects // Refresh jQuery objects
$(".validate").click(in_validate); $(".validate").click(de_validate);
refreshBalance(); refreshBalance();
// error if this method doesn't exist. Please define it. // error if this method doesn't exist. Please define it.

View File

@ -24,13 +24,10 @@ $(document).ready(function() {
}); });
// Switching in double consumptions mode should update the layout // Switching in double consumptions mode should update the layout
let double_conso_obj = $("#double_conso"); $("#double_conso").click(function() {
double_conso_obj.click(function() { $("#consos_list_div").removeClass('d-none');
$("#consos_list_div").show();
$("#infos_div").attr('class', 'col-sm-5 col-xl-6');
$("#note_infos_div").attr('class', 'col-xl-3');
$("#user_select_div").attr('class', 'col-xl-4'); $("#user_select_div").attr('class', 'col-xl-4');
$("#buttons_div").attr('class', 'col-sm-7 col-xl-6'); $("#infos_div").attr('class', 'col-sm-5 col-xl-6');
let note_list_obj = $("#note_list"); let note_list_obj = $("#note_list");
if (buttons.length > 0 && note_list_obj.text().length > 0) { if (buttons.length > 0 && note_list_obj.text().length > 0) {
@ -44,13 +41,10 @@ $(document).ready(function() {
} }
}); });
let single_conso_obj = $("#single_conso"); $("#single_conso").click(function() {
single_conso_obj.click(function() { $("#consos_list_div").addClass('d-none');
$("#consos_list_div").hide();
$("#infos_div").attr('class', 'col-sm-5 col-md-4');
$("#note_infos_div").attr('class', 'col-xl-5');
$("#user_select_div").attr('class', 'col-xl-7'); $("#user_select_div").attr('class', 'col-xl-7');
$("#buttons_div").attr('class', 'col-sm-7 col-md-8'); $("#infos_div").attr('class', 'col-sm-5 col-md-4');
let consos_list_obj = $("#consos_list"); let consos_list_obj = $("#consos_list");
if (buttons.length > 0) { if (buttons.length > 0) {
@ -69,12 +63,8 @@ $(document).ready(function() {
} }
}); });
// Ensure we begin in single consumption. Removing these lines may cause problems when reloading. // Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS
single_conso_obj.prop('checked', 'true'); $("label[for='double_conso']").removeClass('active');
double_conso_obj.removeAttr('checked');
$("label[for='double_conso']").attr('class', 'btn btn-sm btn-outline-primary');
$("#consos_list_div").hide();
$("#consume_all").click(consumeAll); $("#consume_all").click(consumeAll);
}); });
@ -84,7 +74,7 @@ notes_display = [];
buttons = []; buttons = [];
// When the user searches an alias, we update the auto-completion // When the user searches an alias, we update the auto-completion
autoCompleteNote("note", "alias_matched", "note_list", notes, notes_display, autoCompleteNote("note", "note_list", notes, notes_display,
"alias", "note", "user_note", "profile_pic", function() { "alias", "note", "user_note", "profile_pic", function() {
if (buttons.length > 0 && $("#single_conso").is(":checked")) { if (buttons.length > 0 && $("#single_conso").is(":checked")) {
consumeAll(); consumeAll();
@ -152,7 +142,6 @@ function reset() {
notes.length = 0; notes.length = 0;
buttons.length = 0; buttons.length = 0;
$("#note_list").html(""); $("#note_list").html("");
$("#alias_matched").html("");
$("#consos_list").html(""); $("#consos_list").html("");
$("#user_note").text(""); $("#user_note").text("");
$("#profile_pic").attr("src", "/media/pic/default.png"); $("#profile_pic").attr("src", "/media/pic/default.png");
@ -167,7 +156,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, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, consume(note_display.note.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);
}); });
}); });

View File

@ -14,8 +14,6 @@ function reset() {
dests.length = 0; dests.length = 0;
$("#source_note_list").html(""); $("#source_note_list").html("");
$("#dest_note_list").html(""); $("#dest_note_list").html("");
$("#source_alias_matched").html("");
$("#dest_alias_matched").html("");
$("#amount").val(""); $("#amount").val("");
$("#reason").val(""); $("#reason").val("");
$("#last_name").val(""); $("#last_name").val("");
@ -28,14 +26,20 @@ function reset() {
} }
$(document).ready(function() { $(document).ready(function() {
autoCompleteNote("source_note", "source_alias_matched", "source_note_list", sources, sources_notes_display, /**
"source_alias", "source_note", "user_note", "profile_pic"); * If we are in credit/debit mode, check that only one note is entered.
autoCompleteNote("dest_note", "dest_alias_matched", "dest_note_list", dests, dests_notes_display, * More over, get first name and last name to autocomplete fields.
"dest_alias", "dest_note", "user_note", "profile_pic", function() { */
function checkUniqueNote() {
if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) { if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) {
let last = dests_notes_display[dests_notes_display.length - 1]; let arr = $("#type_credit").is(":checked") ? dests_notes_display : sources_notes_display;
dests_notes_display.length = 0;
dests_notes_display.push(last); if (arr.length === 0)
return;
let last = arr[arr.length - 1];
arr.length = 0;
arr.push(last);
last.quantity = 1; last.quantity = 1;
@ -57,8 +61,75 @@ $(document).ready(function() {
} }
return true; return true;
}
autoCompleteNote("source_note", "source_note_list", sources, sources_notes_display,
"source_alias", "source_note", "user_note", "profile_pic", checkUniqueNote);
autoCompleteNote("dest_note", "dest_note_list", dests, dests_notes_display,
"dest_alias", "dest_note", "user_note", "profile_pic", checkUniqueNote);
let source = $("#source_note");
let dest = $("#dest_note");
$("#type_gift").click(function() {
$("#special_transaction_div").addClass('d-none');
source.attr('disabled', true);
source.val(username);
source.tooltip('hide');
$("#source_note_list").addClass('d-none');
dest.attr('disabled', false);
$("#dest_note_list").removeClass('d-none');
}); });
$("#type_transfer").click(function() {
$("#special_transaction_div").addClass('d-none');
source.attr('disabled', false);
$("#source_note_list").removeClass('d-none');
dest.attr('disabled', false);
$("#dest_note_list").removeClass('d-none');
});
$("#type_credit").click(function() {
$("#special_transaction_div").removeClass('d-none');
$("#source_note_list").addClass('d-none');
$("#dest_note_list").removeClass('d-none');
source.attr('disabled', true);
source.val($("#credit_type option:selected").text());
source.tooltip('hide');
dest.attr('disabled', false);
dest.val('');
dest.tooltip('hide');
if (dests_notes_display.length > 1) {
$("#dest_note_list").html('');
dests_notes_display.length = 0;
}
});
$("#type_debit").click(function() {
$("#special_transaction_div").removeClass('d-none');
$("#source_note_list").removeClass('d-none');
$("#dest_note_list").addClass('d-none');
source.attr('disabled', false);
source.val('');
source.tooltip('hide');
dest.attr('disabled', true);
dest.val($("#credit_type option:selected").text());
dest.tooltip('hide');
if (sources_notes_display.length > 1) {
$("#source_note_list").html('');
sources_notes_display.length = 0;
}
});
$("#credit_type").change(function() {
let type = $("#credit_type option:selected").text();
if ($("#type_credit").is(":checked"))
source.val(type);
else
dest.val(type);
});
// Ensure we begin in gift mode. Removing these lines may cause problems when reloading. // Ensure we begin in gift mode. Removing these lines may cause problems when reloading.
let type_gift = $("#type_gift"); // Default mode let type_gift = $("#type_gift"); // Default mode
@ -91,7 +162,7 @@ $("#btn_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.note.id,
"destination_alias": dest.name "destination_alias": dest.name
}).done(function () { }).done(function () {
addMsg("Le transfert de " addMsg("Le transfert de "
@ -99,7 +170,7 @@ $("#btn_transfer").click(function() {
+ " 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(); reset();
}).fail(function () { }).fail(function () { // do it again but valid = false
$.post("/api/note/transaction/transaction/", $.post("/api/note/transaction/transaction/",
{ {
"csrfmiddlewaretoken": CSRF_TOKEN, "csrfmiddlewaretoken": CSRF_TOKEN,
@ -111,7 +182,7 @@ $("#btn_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.note.id,
"destination_alias": dest.name "destination_alias": dest.name
}).done(function () { }).done(function () {
addMsg("Le transfert de " addMsg("Le transfert de "
@ -141,9 +212,9 @@ $("#btn_transfer").click(function() {
"valid": true, "valid": true,
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
"resourcetype": "Transaction", "resourcetype": "Transaction",
"source": source.id, "source": source.note.id,
"source_alias": source.name, "source_alias": source.name,
"destination": dest.id, "destination": dest.note.id,
"destination_alias": dest.name "destination_alias": dest.name
}).done(function () { }).done(function () {
addMsg("Le transfert de " addMsg("Le transfert de "
@ -151,7 +222,7 @@ $("#btn_transfer").click(function() {
+ " 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(); reset();
}).fail(function (err) { }).fail(function (err) { // do it again but valid = false
$.post("/api/note/transaction/transaction/", $.post("/api/note/transaction/transaction/",
{ {
"csrfmiddlewaretoken": CSRF_TOKEN, "csrfmiddlewaretoken": CSRF_TOKEN,
@ -162,9 +233,9 @@ $("#btn_transfer").click(function() {
"invalidity_reason": "Solde insuffisant", "invalidity_reason": "Solde insuffisant",
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
"resourcetype": "Transaction", "resourcetype": "Transaction",
"source": source.id, "source": source.note.id,
"source_alias": source.name, "source_alias": source.name,
"destination": dest.id, "destination": dest.note.id,
"destination_alias": dest.name "destination_alias": dest.name
}).done(function () { }).done(function () {
addMsg("Le transfert de " addMsg("Le transfert de "
@ -184,10 +255,11 @@ $("#btn_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;
let given_reason = $("#reason").val(); let given_reason = $("#reason").val();
let source, dest, reason; let source, dest, reason;
if ($("#type_credit").is(':checked')) { if ($("#type_credit").is(':checked')) {
user_note = dests_notes_display[0].note.id;
source = special_note; source = special_note;
dest = user_note; dest = user_note;
reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase(); reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase();
@ -195,6 +267,7 @@ $("#btn_transfer").click(function() {
reason += " (" + given_reason + ")"; reason += " (" + given_reason + ")";
} }
else { else {
user_note = sources_notes_display[0].note.id;
source = user_note; source = user_note;
dest = special_note; dest = special_note;
reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase(); reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase();

View File

@ -58,6 +58,20 @@ SPDX-License-Identifier: GPL-3.0-or-later
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
.tooltip.show {
opacity: 1; /* opaque tooltip */
}
.tooltip-inner {
background-color: #fff;
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15);
border: 1px solid rgba(0,0,0,.250);
color: #000;
margin: 0 .5rem .25rem .5rem;
padding: 0;
}
.bs-tooltip-bottom .arrow::before {
border-bottom-color: rgba(0,0,0,.250);
}
</style> </style>
{% block extracss %}{% endblock %} {% block extracss %}{% endblock %}

View File

@ -10,10 +10,10 @@
<div class="col-sm-5 col-md-4" id="infos_div"> <div class="col-sm-5 col-md-4" id="infos_div">
<div class="row"> <div class="row">
{# User details column #} {# User details column #}
<div class="col-xl-5" id="note_infos_div"> <div class="col">
<div class="card border-success shadow mb-4"> <div class="card border-success shadow mb-4 text-center">
<img src="/media/pic/default.png" <img src="/media/pic/default.png"
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block"> id="profile_pic" alt="" class="card-img-top">
<div class="card-body text-center"> <div class="card-body text-center">
<span id="user_note"></span> <span id="user_note"></span>
</div> </div>
@ -25,38 +25,45 @@
<div class="card border-success shadow mb-4"> <div class="card border-success shadow mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
{% trans "Select emitters" %} {% trans "Consum" %}
</p> </p>
</div> </div>
<div class="card-body p-0" style="min-height:125px;">
<ul class="list-group list-group-flush" id="note_list"> <ul class="list-group list-group-flush" id="note_list">
</ul> </ul>
<div class="card-body"> </div>
<input class="form-control mx-auto d-block" type="text" id="note" />
<ul class="list-group list-group-flush" id="alias_matched"> {# User search with autocompletion #}
</ul> <div class="card-footer">
<input class="form-control mx-auto d-block"
placeholder="{% trans "Name or alias..." %}" type="text" id="note" autofocus />
</div> </div>
</div> </div>
</div> </div>
<div class="col-xl-5" id="consos_list_div"> <div class="col-xl-5 d-none" id="consos_list_div">
<div class="card border-info shadow mb-4"> <div class="card border-info shadow mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
{% trans "Select consumptions" %} {% trans "Select consumptions" %}
</p> </p>
</div> </div>
<div class="card-body p-0" style="min-height:125px;">
<ul class="list-group list-group-flush" id="consos_list"> <ul class="list-group list-group-flush" id="consos_list">
</ul> </ul>
<button id="consume_all" class="form-control btn btn-primary"> </div>
<div class="card-footer text-center">
<a id="consume_all" href="#" class="btn btn-primary">
{% trans "Consume!" %} {% trans "Consume!" %}
</button> </a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{# Buttons column #} {# Buttons column #}
<div class="col-sm-7 col-md-8" id="buttons_div"> <div class="col">
{# Show last used buttons #} {# Show last used buttons #}
<div class="card shadow mb-4"> <div class="card shadow mb-4">
<div class="card-header"> <div class="card-header">
@ -123,10 +130,12 @@
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons"> <div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
<label for="single_conso" class="btn btn-sm btn-outline-primary active"> <label for="single_conso" class="btn btn-sm btn-outline-primary active">
<input type="radio" name="conso_type" id="single_conso" checked> <input type="radio" name="conso_type" id="single_conso" checked>
<i class="fa fa-long-arrow-left" aria-hidden="true"></i>
{% trans "Single consumptions" %} {% trans "Single consumptions" %}
</label> </label>
<label for="double_conso" class="btn btn-sm btn-outline-primary"> <label for="double_conso" class="btn btn-sm btn-outline-primary">
<input type="radio" name="conso_type" id="double_conso"> <input type="radio" name="conso_type" id="double_conso">
<i class="fa fa-arrows-h" aria-hidden="true"></i>
{% trans "Double consumptions" %} {% trans "Double consumptions" %}
</label> </label>
</div> </div>
@ -146,7 +155,7 @@
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}
<script type="text/javascript" src="/static/js/consos.js"></script> <script type="text/javascript" src="{% static "js/consos.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
{% for button in most_used %} {% for button in most_used %}
{% if button.display %} {% if button.display %}

View File

@ -38,8 +38,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-3" id="note_infos_div">
<div class="col-xl-4" id="note_infos_div">
<div class="card border-success shadow mb-4"> <div class="card border-success shadow mb-4">
<img src="/media/pic/default.png" <img src="/media/pic/default.png"
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block"> id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block">
@ -48,7 +47,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-4" id="emitters_div" style="display: none;">
<div class="col-md-3" id="emitters_div">
<div class="card border-success shadow mb-4"> <div class="card border-success shadow mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
@ -58,24 +58,53 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ul class="list-group list-group-flush" id="source_note_list"> <ul class="list-group list-group-flush" id="source_note_list">
</ul> </ul>
<div class="card-body"> <div class="card-body">
<input class="form-control mx-auto d-block" type="text" id="source_note" /> <input class="form-control mx-auto d-block" type="text" id="source_note" placeholder="{% trans "Name or alias..." %}" />
<ul class="list-group list-group-flush" id="source_alias_matched"> </div>
</div>
</div>
<div class="col-md-3" id="dests_div">
<div class="card border-info shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold" id="dest_title">
{% trans "Select receivers" %}
</p>
</div>
<ul class="list-group list-group-flush" id="dest_note_list">
</ul>
<div class="card-body">
<input class="form-control mx-auto d-block" type="text" id="dest_note" placeholder="{% trans "Name or alias..." %}" />
<ul class="list-group list-group-flush" id="dest_alias_matched">
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
{% if "note.notespecial"|not_empty_model_list %} <div class="col-md-3" id="external_div">
<div class="col-md-4" id="external_div" style="display: none;"> <div class="card border-warning shadow mb-4">
<div class="card border-success shadow mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
{% trans "External payment" %} {% trans "Action" %}
</p> </p>
</div> </div>
<ul class="list-group list-group-flush" id="source_note_list"> <ul class="list-group list-group-flush" id="source_note_list">
</ul> </ul>
<div class="card-body"> <div class="card-body">
<div class="form-row">
<div class="col-md-12">
<label for="amount">{% trans "Amount" %} :</label>
{% include "note/amount_input.html" with widget=amount_widget %}
</div>
</div>
<div class="form-row">
<div class="col-md-12">
<label for="reason">{% trans "Reason" %} :</label>
<input class="form-control mx-auto d-block" type="text" id="reason" required />
</div>
</div>
<div class="d-none" id="special_transaction_div">
<div class="form-row"> <div class="form-row">
<div class="col-md-12"> <div class="col-md-12">
<label for="credit_type">{% trans "Transfer type" %} :</label> <label for="credit_type">{% trans "Transfer type" %} :</label>
@ -105,46 +134,16 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
</div> </div>
</div> </div>
</div> <hr>
</div>
{% endif %}
<div class="col-md-8" id="dests_div">
<div class="card border-info shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold" id="dest_title">
{% trans "Select receivers" %}
</p>
</div>
<ul class="list-group list-group-flush" id="dest_note_list">
</ul>
<div class="card-body">
<input class="form-control mx-auto d-block" type="text" id="dest_note" />
<ul class="list-group list-group-flush" id="dest_alias_matched">
</ul>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="amount">{% trans "Amount" %} :</label>
{% include "note/amount_input.html" with widget=amount_widget %}
</div>
<div class="form-group col-md-6">
<label for="reason">{% trans "Reason" %} :</label>
<input class="form-control mx-auto d-block" type="text" id="reason" required />
</div>
</div>
<div class="form-row"> <div class="form-row">
<div class="col-md-12"> <div class="col-md-12">
<button id="btn_transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button> <button id="btn_transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
<div class="card shadow mb-4" id="history"> <div class="card shadow mb-4" id="history">
<div class="card-header"> <div class="card-header">
@ -161,34 +160,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }}; TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }};
SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }}; SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }};
user_id = {{ user.note.pk }}; user_id = {{ user.note.pk }};
username = "{{ user.username }}";
$("#type_gift").click(function() {
$("#emitters_div").hide();
$("#external_div").hide();
$("#dests_div").attr('class', 'col-md-8');
$("#dest_title").text("{% trans "Select receivers" %}");
});
$("#type_transfer").click(function() {
$("#external_div").hide();
$("#emitters_div").show();
$("#dests_div").attr('class', 'col-md-4');
$("#dest_title").text("{% trans "Select receivers" %}");
});
$("#type_credit").click(function() {
$("#emitters_div").hide();
$("#external_div").show();
$("#dests_div").attr('class', 'col-md-4');
$("#dest_title").text("{% trans "Credit note" %}");
});
$("#type_debit").click(function() {
$("#emitters_div").hide();
$("#external_div").show();
$("#dests_div").attr('class', 'col-md-4');
$("#dest_title").text("{% trans "Debit note" %}");
});
</script> </script>
<script src="/static/js/transfer.js"></script> <script src="/static/js/transfer.js"></script>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,28 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% load pretty_money %}
{% block content %} {% block content %}
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Buttons list" %}</a></p> <p>
<a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Buttons list" %}</a>
</p>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form|crispy}}
<button class="btn btn-primary" type="submit">Submit</button> <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form> </form>
{% if price_history and price_history.1 %}
<hr>
<h4>{% trans "Price history" %}</h4>
<ul>
{% for price in price_history %}
<li>{{ price.price|pretty_money }} {% if price.time %}({% trans "Obsolete since" %} {{ price.time }}){% else %}({% trans "Current price" %}){% endif %}</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -6,9 +6,17 @@
<div class="row justify-content-center mb-4"> <div class="row justify-content-center mb-4">
<div class="col-md-10 text-center"> <div class="col-md-10 text-center">
<h4> <h4>
{% trans "search button" %} {% trans "Search button" %}
</h4> </h4>
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/> <input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button..." %}">
<div class="form-group">
<div id="div_active_only" class="form-check">
<label for="active_only" class="form-check-label">
<input type="checkbox" name="active_only" class="checkboxinput form-check-input" checked="" id="active_only">
{% trans "Display visible buttons only" %}
</label>
</div>
</div>
<hr> <hr>
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}">{% trans "New button" %}</a> <a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}">{% trans "New button" %}</a>
</div> </div>
@ -33,29 +41,37 @@
function getInfo() { function getInfo() {
var asked = $("#search_field").val(); var asked = $("#search_field").val();
/* on ne fait la requête que si on a au moins un caractère pour chercher */ /* 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) { if (asked.length >= 1) {
$.getJSON("/api/note/transaction/template/?format=json&search="+asked, function(buttons){ $.getJSON("/api/note/transaction/template/?format=json&search=" + asked + ($("#active_only").is(":checked") ? "&display=true" : ""), function(buttons) {
console.log(buttons);
let selected_id = buttons.results.map((a => "#row-" + a.id)); let selected_id = buttons.results.map((a => "#row-" + a.id));
$(".table-row,"+selected_id.join()).show(); console.log(".table-row " + selected_id.join());
$(".table-row").not(selected_id.join()).hide(); $(".table-row " + selected_id.join()).removeClass('d-none');
$(".table-row").not(selected_id.join()).addClass('d-none');
}); });
}else{ }
else {
if ($("#active_only").is(":checked")) {
$('.table-success').removeClass('d-none');
$('.table-danger').addClass('d-none');
}
else {
// show everything // show everything
$('table tr').show(); $('table tr').removeClass('d-none');
} }
} }
}
var timer; var timer;
var timer_on; var timer_on;
/* Fontion appelée quand le texte change (délenche le timer) */ /* Fontion appelée quand le texte change (délenche le timer) */
function search_field_moved(secondfield) { function search_field_moved() {
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur. if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout("getInfo(" + secondfield + ")", 300); timer = setTimeout(getInfo, 300);
} }
else { // Sinon, on le lance et on enregistre le fait qu'il tourne. else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
timer = setTimeout("getInfo(" + secondfield + ")", 300); timer = setTimeout(getInfo, 300);
timer_on = true; timer_on = true;
} }
} }
@ -74,5 +90,12 @@ function search_field_moved(secondfield) {
addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' ) addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' )
}); });
} }
$(document).ready(function() {
$("#search_field").keyup(search_field_moved);
$("#active_only").change(search_field_moved);
search_field_moved();
});
</script> </script>
{% endblock %} {% endblock %}