diff --git a/apps/activity/views.py b/apps/activity/views.py index 12386bd1..ef144460 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -3,6 +3,7 @@ from datetime import datetime, timezone +from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.db.models import F, Q @@ -138,8 +139,14 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): | Q(note__noteuser__user__last_name__regex=pattern) | Q(name__regex=pattern) | Q(normalized_name__regex=Alias.normalize(pattern)))) \ - .filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))\ - .distinct()[:20] + .filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")) + 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: note.type = "Adhérent" note.activity = activity diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index 5625e5b5..c61ccd42 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -3,6 +3,8 @@ from rest_framework import serializers 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.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ @@ -97,6 +99,35 @@ class NotePolymorphicSerializer(PolymorphicSerializer): 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): """ REST API Serializer for Transaction templates. diff --git a/apps/note/api/urls.py b/apps/note/api/urls.py index 796a397f..2f3fd1a9 100644 --- a/apps/note/api/urls.py +++ b/apps/note/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import NotePolymorphicViewSet, AliasViewSet, \ +from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \ TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet @@ -11,6 +11,7 @@ def register_note_urls(router, path): """ router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/alias', AliasViewSet) + router.register(path + '/consumer', ConsumerViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/transaction', TransactionViewSet) diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 23bed1c9..d5ad8129 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -10,8 +10,8 @@ from rest_framework.response import Response from rest_framework import status from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet -from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \ - TransactionTemplateSerializer, TransactionPolymorphicSerializer +from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ + TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer from ..models.notes import Note, Alias from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory @@ -90,6 +90,30 @@ class AliasViewSet(ReadProtectedModelViewSet): 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): """ REST API View set. diff --git a/apps/note/forms.py b/apps/note/forms.py index 50f226f2..bc479e20 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -4,7 +4,7 @@ from django import forms from django.contrib.contenttypes.models import ContentType 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 @@ -24,11 +24,6 @@ class TransactionTemplateForm(forms.ModelForm): model = TransactionTemplate 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 = { 'destination': Autocomplete( @@ -41,4 +36,5 @@ class TransactionTemplateForm(forms.ModelForm): 'placeholder': 'Note ...', }, ), + 'amount': AmountInput(), } diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 081f6022..366b4527 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -227,8 +227,7 @@ class RecurrentTransaction(Transaction): template = models.ForeignKey( TransactionTemplate, - null=True, - on_delete=models.SET_NULL, + on_delete=models.PROTECT, ) category = models.ForeignKey( TemplateCategory, diff --git a/apps/note/tables.py b/apps/note/tables.py index a38beb9a..a1385abc 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -55,7 +55,7 @@ class HistoryTable(tables.Table): "class": lambda record: str(record.valid).lower() + ' validate', "data-toggle": "tooltip", "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_' + str(record.id) + '").show();$("#invalidity_reason_' + str(record.id) + '").focus();', @@ -129,13 +129,14 @@ class ButtonTable(tables.Table): 'table table-bordered condensed table-hover' } 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), 'data-href': lambda record: record.pk } model = TransactionTemplate exclude = ('id',) + order_by = ('type', '-display', 'destination__name', 'name',) edit = tables.LinkColumn('note:template_update', args=[A('pk')], diff --git a/apps/note/views.py b/apps/note/views.py index b26e6cf1..6c0ec1e2 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -1,5 +1,6 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +import json from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin @@ -80,6 +81,33 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up form_class = TransactionTemplateForm 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): """ diff --git a/apps/permission/backends.py b/apps/permission/backends.py index 3dd47fa7..97cded70 100644 --- a/apps/permission/backends.py +++ b/apps/permission/backends.py @@ -8,6 +8,7 @@ from django.contrib.auth.models import User, AnonymousUser from django.contrib.contenttypes.models import ContentType from django.db.models import Q, F from note.models import Note, NoteUser, NoteClub, NoteSpecial +from note_kfet import settings from note_kfet.middlewares import get_current_session from member.models import Membership, Club @@ -107,7 +108,7 @@ class PermissionBackend(ModelBackend): # Anonymous users can't do anything 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 return Q() @@ -141,9 +142,9 @@ class PermissionBackend(ModelBackend): sess = get_current_session() 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 if obj is None: diff --git a/apps/treasury/signals.py b/apps/treasury/signals.py index 54c19c09..188be1a7 100644 --- a/apps/treasury/signals.py +++ b/apps/treasury/signals.py @@ -1,6 +1,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from note.models import NoteSpecial 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 """ - 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() diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 408123ff..ec0abeb7 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -205,7 +205,7 @@ msgstr "" msgid "Balance" msgstr "" -#: apps/activity/views.py:45 templates/base.html:106 +#: apps/activity/views.py:46 templates/base.html:121 msgid "Activities" msgstr "" @@ -245,7 +245,7 @@ msgstr "" msgid "create" 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 msgid "edit" msgstr "" @@ -751,7 +751,7 @@ msgstr "" #: apps/note/models/transactions.py:216 #: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/note/transaction_form.html:19 -#: templates/note/transaction_form.html:145 +#: templates/note/transaction_form.html:140 msgid "Transfer" msgstr "" @@ -810,11 +810,11 @@ msgstr "" msgid "Edit" msgstr "" -#: apps/note/views.py:40 +#: apps/note/views.py:41 msgid "Transfer money" msgstr "" -#: apps/note/views.py:109 templates/base.html:79 +#: apps/note/views.py:137 templates/base.html:94 msgid "Consumptions" msgstr "" @@ -944,7 +944,7 @@ msgid "" "The entered amount is not enough for the memberships, should be at least {}" msgstr "" -#: apps/treasury/apps.py:12 templates/base.html:111 +#: apps/treasury/apps.py:12 templates/base.html:126 msgid "Treasury" msgstr "" @@ -1509,15 +1509,15 @@ msgstr "" msgid "The ENS Paris-Saclay BDE note." msgstr "" -#: templates/base.html:89 +#: templates/base.html:104 msgid "Users" msgstr "" -#: templates/base.html:94 +#: templates/base.html:109 msgid "Clubs" msgstr "" -#: templates/base.html:100 +#: templates/base.html:115 msgid "Registrations" msgstr "" @@ -1655,31 +1655,36 @@ msgstr "" msgid "There is no user with this pattern." msgstr "" -#: templates/note/conso_form.html:28 templates/note/transaction_form.html:55 -msgid "Select emitters" +#: templates/note/conso_form.html:28 +msgid "Consum" 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" msgstr "" -#: templates/note/conso_form.html:51 +#: templates/note/conso_form.html:57 msgid "Consume!" msgstr "" -#: templates/note/conso_form.html:64 +#: templates/note/conso_form.html:71 msgid "Most used buttons" msgstr "" -#: templates/note/conso_form.html:126 +#: templates/note/conso_form.html:134 msgid "Single consumptions" msgstr "" -#: templates/note/conso_form.html:130 +#: templates/note/conso_form.html:139 msgid "Double consumptions" 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" msgstr "" @@ -1687,53 +1692,67 @@ msgstr "" msgid "Gift" msgstr "" -#: templates/note/transaction_form.html:73 -msgid "External payment" +#: templates/note/transaction_form.html:55 +msgid "Select emitters" msgstr "" -#: templates/note/transaction_form.html:81 -msgid "Transfer type" -msgstr "" - -#: templates/note/transaction_form.html:116 -#: templates/note/transaction_form.html:169 -#: templates/note/transaction_form.html:176 +#: templates/note/transaction_form.html:70 msgid "Select receivers" msgstr "" -#: templates/note/transaction_form.html:138 +#: templates/note/transaction_form.html:87 +msgid "Action" +msgstr "" + +#: templates/note/transaction_form.html:102 msgid "Reason" msgstr "" -#: templates/note/transaction_form.html:183 -msgid "Credit note" +#: templates/note/transaction_form.html:110 +msgid "Transfer type" msgstr "" -#: templates/note/transaction_form.html:190 -msgid "Debit note" -msgstr "" - -#: templates/note/transactiontemplate_form.html:6 +#: templates/note/transactiontemplate_form.html:10 msgid "Buttons list" msgstr "" -#: templates/note/transactiontemplate_list.html:9 -msgid "search button" +#: templates/note/transactiontemplate_form.html:21 +msgid "Price history" 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" msgstr "" -#: templates/note/transactiontemplate_list.html:20 +#: templates/note/transactiontemplate_list.html:28 msgid "buttons listing " msgstr "" -#: templates/note/transactiontemplate_list.html:70 +#: templates/note/transactiontemplate_list.html:86 msgid "button successfully deleted " msgstr "" -#: templates/note/transactiontemplate_list.html:74 +#: templates/note/transactiontemplate_list.html:90 msgid "Unable to delete button " msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 65c41e93..f23c1913 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -206,7 +206,7 @@ msgstr "Note" msgid "Balance" msgstr "Solde du compte" -#: apps/activity/views.py:45 templates/base.html:106 +#: apps/activity/views.py:46 templates/base.html:121 msgid "Activities" msgstr "Activités" @@ -246,7 +246,7 @@ msgstr "Nouvelles données" msgid "create" 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 msgid "edit" msgstr "Modifier" @@ -759,7 +759,7 @@ msgstr "transactions" #: apps/note/models/transactions.py:216 #: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/note/transaction_form.html:19 -#: templates/note/transaction_form.html:145 +#: templates/note/transaction_form.html:140 msgid "Transfer" msgstr "Virement" @@ -818,11 +818,11 @@ msgstr "Supprimer" msgid "Edit" msgstr "Éditer" -#: apps/note/views.py:40 +#: apps/note/views.py:41 msgid "Transfer money" 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" msgstr "Consommations" @@ -965,7 +965,7 @@ msgstr "" "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" msgstr "Trésorerie" @@ -1558,15 +1558,15 @@ msgstr "Toutes les activités" msgid "The ENS Paris-Saclay BDE note." msgstr "La note du BDE de l'ENS Paris-Saclay." -#: templates/base.html:89 +#: templates/base.html:104 msgid "Users" msgstr "Utilisateurs" -#: templates/base.html:94 +#: templates/base.html:109 msgid "Clubs" msgstr "Clubs" -#: templates/base.html:100 +#: templates/base.html:115 msgid "Registrations" msgstr "Inscriptions" @@ -1709,31 +1709,36 @@ msgstr "Sauvegarder les changements" msgid "There is no user with this pattern." 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 -msgid "Select emitters" -msgstr "Sélection des émetteurs" +#: templates/note/conso_form.html:28 +msgid "Consum" +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" msgstr "Sélection des consommations" -#: templates/note/conso_form.html:51 +#: templates/note/conso_form.html:57 msgid "Consume!" msgstr "Consommer !" -#: templates/note/conso_form.html:64 +#: templates/note/conso_form.html:71 msgid "Most used buttons" msgstr "Boutons les plus utilisés" -#: templates/note/conso_form.html:126 +#: templates/note/conso_form.html:134 msgid "Single consumptions" msgstr "Consommations simples" -#: templates/note/conso_form.html:130 +#: templates/note/conso_form.html:139 msgid "Double consumptions" 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" msgstr "Historique des transactions récentes" @@ -1741,53 +1746,67 @@ msgstr "Historique des transactions récentes" msgid "Gift" msgstr "Don" -#: templates/note/transaction_form.html:73 -msgid "External payment" -msgstr "Paiement externe" +#: templates/note/transaction_form.html:55 +msgid "Select emitters" +msgstr "Sélection des émetteurs" -#: templates/note/transaction_form.html:81 -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 +#: templates/note/transaction_form.html:70 msgid "Select receivers" 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" msgstr "Raison" -#: templates/note/transaction_form.html:183 -msgid "Credit note" -msgstr "Note à recharger" +#: templates/note/transaction_form.html:110 +msgid "Transfer type" +msgstr "Type de transfert" -#: templates/note/transaction_form.html:190 -msgid "Debit note" -msgstr "Note à débiter" - -#: templates/note/transactiontemplate_form.html:6 +#: templates/note/transactiontemplate_form.html:10 msgid "Buttons list" 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 -msgid "search button" +msgid "Search button" 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" msgstr "Nouveau bouton" -#: templates/note/transactiontemplate_list.html:20 +#: templates/note/transactiontemplate_list.html:28 msgid "buttons listing " msgstr "Liste des boutons" -#: templates/note/transactiontemplate_list.html:70 +#: templates/note/transactiontemplate_list.html:86 msgid "button successfully deleted " msgstr "Le bouton a bien été supprimé" -#: templates/note/transactiontemplate_list.html:74 +#: templates/note/transactiontemplate_list.html:90 msgid "Unable to delete button " msgstr "Impossible de supprimer le bouton " diff --git a/media/pic/default.png b/media/pic/default.png index f933bc34..41a31a1c 100644 Binary files a/media/pic/default.png and b/media/pic/default.png differ diff --git a/static/js/base.js b/static/js/base.js index f9f040c1..b04f71cb 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -21,7 +21,7 @@ function pretty_money(value) { * @param alert_type The type of the alert. Choices: info, success, warning, danger * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ -function addMsg(msg, alert_type, timeout=-1) { +function addMsg(msg, alert_type, timeout = -1) { let msgDiv = $("#messages"); let html = msgDiv.html(); let id = Math.floor(10000 * Math.random() + 1); @@ -42,28 +42,28 @@ function addMsg(msg, alert_type, timeout=-1) { * @param errs_obj [{error_code:erro_message}] * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ -function errMsg(errs_obj, timeout=-1) { +function errMsg(errs_obj, timeout = -1) { for (const err_msg of Object.values(errs_obj)) { - addMsg(err_msg,'danger', timeout); - } + addMsg(err_msg, 'danger', timeout); + } } var reloadWithTurbolinks = (function () { - var scrollPosition; + var scrollPosition; - function reload () { - scrollPosition = [window.scrollX, window.scrollY]; - Turbolinks.visit(window.location.toString(), { action: 'replace' }) - } - - document.addEventListener('turbolinks:load', function () { - if (scrollPosition) { - window.scrollTo.apply(window, scrollPosition); - scrollPosition = null + function reload() { + scrollPosition = [window.scrollX, window.scrollY]; + Turbolinks.visit(window.location.toString(), {action: 'replace'}) } - }); - return reload; + document.addEventListener('turbolinks:load', function () { + if (scrollPosition) { + window.scrollTo.apply(window, scrollPosition); + scrollPosition = null + } + }); + + return reload; })(); /** @@ -79,17 +79,36 @@ function refreshBalance() { * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. */ 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
  • entry with a given id and text */ -function li(id, text) { - return "
  • " + text + "
  • \n"; +function li(id, text, extra_css) { + return "
  • " + text + "
  • \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 * @param note The note to render @@ -97,27 +116,29 @@ function li(id, text) { * @param user_note_field * @param profile_pic_field */ -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) { 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; - if (alias !== note.name) + if (alias !== note.name && note.name) alias += " (aka. " + note.name + ")"; - if (user_note_field !== null) - $("#" + user_note_field).text(alias + (note.balance == null ? "" : (" : " + pretty_money(note.balance)))); - if (profile_pic_field != null) - $("#" + profile_pic_field).attr('src', img); + if (user_note_field !== null) { + $("#" + user_note_field).removeAttr('class'); + $("#" + 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).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; + } + }); + } + } } /** @@ -132,8 +153,8 @@ function displayNote(note, alias, user_note_field=null, profile_pic_field=null) * (useful in consumptions, put null if not used) * @returns an anonymous function to be compatible with jQuery events */ -function removeNote(d, note_prefix="note", notes_display, note_list_id, user_note_field=null, profile_pic_field=null) { - return (function() { +function removeNote(d, note_prefix = "note", notes_display, note_list_id, user_note_field = null, profile_pic_field = null) { + return (function () { let new_notes_display = []; let html = ""; notes_display.forEach(function (disp) { @@ -141,12 +162,13 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not disp.quantity -= disp.id === d.id ? 1 : 0; new_notes_display.push(disp); html += li(note_prefix + "_" + disp.id, disp.name - + "" + disp.quantity + ""); + + "" + disp.quantity + "", + displayStyle(disp.note)); } }); notes_display.length = 0; - new_notes_display.forEach(function(disp) { + new_notes_display.forEach(function (disp) { notes_display.push(disp); }); @@ -154,7 +176,7 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not notes_display.forEach(function (disp) { let obj = $("#" + note_prefix + "_" + disp.id); obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field)); - obj.hover(function() { + obj.hover(function () { if (disp.note) displayNote(disp.note, disp.name, user_note_field, profile_pic_field); }); @@ -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 * @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 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] @@ -179,143 +200,145 @@ function removeNote(d, note_prefix="note", notes_display, note_list_id, user_not * the associated note is not displayed. * 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", - note_prefix="note", user_note_field=null, profile_pic_field=null, alias_click=null) { +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) { let field = $("#" + field_id); - // When the user clicks on the search field, it is immediately cleared - field.click(function() { + + // Configure tooltip + field.tooltip({ + html: true, + placement: 'bottom', + title: 'Loading...', + trigger: 'manual', + container: field.parent() + }); + + // Clear search on click + field.click(function () { + field.tooltip('hide'); field.val(""); }); let old_pattern = null; - // When the user type "Enter", the first alias is clicked, and the informations are displayed - field.keypress(function(event) { - if (event.originalEvent.charCode === 13) { - let li_obj = $("#" + alias_matched_id + " li").first(); + // When the user type "Enter", the first alias is clicked + field.keypress(function (event) { + if (event.originalEvent.charCode === 13 && notes.length > 0) { + let li_obj = field.parent().find("ul li").first(); displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field); li_obj.trigger("click"); } }); // When the user type something, the matched aliases are refreshed - field.keyup(function(e) { + field.keyup(function (e) { if (e.originalEvent.charCode === 13) return; let pattern = field.val(); + // If the pattern is not modified, we don't query the API - if (pattern === old_pattern || pattern === "") + if (pattern === old_pattern) return; - old_pattern = pattern; - - // Clear old matched notes notes.length = 0; - let aliases_matched_obj = $("#" + alias_matched_id); - let aliases_matched_html = ""; + // get matched Alias with note associated + if (pattern === "") { + field.tooltip('hide'); + notes.length = 0; + return; + } - // Get matched notes with the given pattern - getMatchedNotes(pattern, function(aliases) { - // The response arrived too late, we stop the request - if (pattern !== $("#" + field_id).val()) - return; + $.getJSON("/api/note/consumer/?format=json&alias=" + + pattern + + "&search=user|club&ordering=normalized_name", + function (consumers) { + // The response arrived too late, we stop the request + if (pattern !== $("#" + field_id).val()) + return; - aliases.results.forEach(function (alias) { - let note = alias.note; - note = { - id: note, - name: alias.name, - alias: alias, - balance: null - }; - aliases_matched_html += li(alias_prefix + "_" + alias.id, alias.name); - notes.push(note); - }); - - // Display the list of matched aliases - aliases_matched_obj.html(aliases_matched_html); - - notes.forEach(function (note) { - let alias = note.alias; - let alias_obj = $("#" + alias_prefix + "_" + alias.id); - // When an alias is hovered, the profile picture and the balance are displayed at the right place - alias_obj.hover(function () { - displayNote(note, alias.name, user_note_field, profile_pic_field); + // Build tooltip content + let aliases_matched_html = '
      '; + consumers.results.forEach(function (consumer) { + let note = consumer.note; + note.email_confirmed = consumer.email_confirmed; + let extra_css = displayStyle(note); + aliases_matched_html += li(alias_prefix + '_' + consumer.id, + consumer.name, + extra_css); + notes.push(note); }); + aliases_matched_html += '
    '; - // 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; - notes_display.forEach(function (d) { - // We compare the note ids - if (d.id === note.id) { - d.quantity += 1; - disp = d; + // Show tooltip + field.attr('data-original-title', aliases_matched_html).tooltip('show'); + + consumers.results.forEach(function (consumer) { + let note = consumer.note; + let consumer_obj = $("#" + alias_prefix + "_" + consumer.id); + consumer_obj.hover(function () { + displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field) + }); + consumer_obj.click(function () { + var disp = null; + notes_display.forEach(function (d) { + // We compare the note ids + if (d.id === note.id) { + d.quantity += 1; + disp = d; + } + }); + // In the other case, we add a new emitter + if (disp == null) { + disp = { + name: consumer.name, + id: consumer.id, + note: note, + quantity: 1 + }; + notes_display.push(disp); } - }); - // In the other case, we add a new emitter - if (disp == null) { - disp = { - name: alias.name, - id: note.id, - note: note, - quantity: 1 - }; - notes_display.push(disp); - } - // If the function alias_click exists, it is called. If it doesn't return true, then the notes are - // note displayed. Useful for a consumption when a button is already clicked - if (alias_click && !alias_click()) - return; + // If the function alias_click exists, it is called. If it doesn't return true, then the notes are + // note displayed. Useful for a consumption when a button is already clicked + if (alias_click && !alias_click()) + return; - let note_list = $("#" + note_list_id); - let html = ""; - notes_display.forEach(function (disp) { - html += li(note_prefix + "_" + disp.id, disp.name - + "" + disp.quantity + ""); - }); - - // Emitters are displayed - note_list.html(html); - - notes_display.forEach(function (disp) { - let line_obj = $("#" + note_prefix + "_" + disp.id); - // Hover an emitter display also the profile picture - line_obj.hover(function () { - displayNote(disp.note, disp.name, user_note_field, profile_pic_field); + let note_list = $("#" + note_list_id); + let html = ""; + notes_display.forEach(function (disp) { + html += li(note_prefix + "_" + disp.id, + disp.name + + "" + + disp.quantity + "", + displayStyle(disp.note)); }); - // When an emitter is clicked, it is removed - line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, - profile_pic_field)); - }); + // Emitters are displayed + note_list.html(html); + + // Update tooltip position + field.tooltip('update'); + + notes_display.forEach(function (disp) { + let line_obj = $("#" + note_prefix + "_" + disp.id); + // Hover an emitter display also the profile picture + line_obj.hover(function () { + displayNote(disp.note, disp.name, user_note_field, profile_pic_field); + }); + + // When an emitter is clicked, it is removed + line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_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(""); // 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({ "url": "/api/note/transaction/transaction/" + id + "/", type: "PATCH", @@ -324,19 +347,19 @@ function in_validate(id, validated) { "X-CSRFTOKEN": CSRF_TOKEN }, data: { - resourcetype: "RecurrentTransaction", - valid: !validated, - invalidity_reason: invalidity_reason, + "resourcetype": "RecurrentTransaction", + "valid": !validated, + "invalidity_reason": invalidity_reason, }, success: function () { // Refresh jQuery objects - $(".validate").click(in_validate); + $(".validate").click(de_validate); refreshBalance(); // error if this method doesn't exist. Please define it. refreshHistory(); }, - error: function(err) { + error: function (err) { addMsg("Une erreur est survenue lors de la validation/dévalidation " + "de cette transaction : " + err.responseText, "danger"); diff --git a/static/js/consos.js b/static/js/consos.js index 20859933..2fa77249 100644 --- a/static/js/consos.js +++ b/static/js/consos.js @@ -24,13 +24,10 @@ $(document).ready(function() { }); // Switching in double consumptions mode should update the layout - let double_conso_obj = $("#double_conso"); - double_conso_obj.click(function() { - $("#consos_list_div").show(); - $("#infos_div").attr('class', 'col-sm-5 col-xl-6'); - $("#note_infos_div").attr('class', 'col-xl-3'); + $("#double_conso").click(function() { + $("#consos_list_div").removeClass('d-none'); $("#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"); 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_obj.click(function() { - $("#consos_list_div").hide(); - $("#infos_div").attr('class', 'col-sm-5 col-md-4'); - $("#note_infos_div").attr('class', 'col-xl-5'); + $("#single_conso").click(function() { + $("#consos_list_div").addClass('d-none'); $("#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"); 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. - single_conso_obj.prop('checked', 'true'); - double_conso_obj.removeAttr('checked'); - $("label[for='double_conso']").attr('class', 'btn btn-sm btn-outline-primary'); - - $("#consos_list_div").hide(); + // Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS + $("label[for='double_conso']").removeClass('active'); $("#consume_all").click(consumeAll); }); @@ -84,7 +74,7 @@ notes_display = []; buttons = []; // 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() { if (buttons.length > 0 && $("#single_conso").is(":checked")) { consumeAll(); @@ -152,7 +142,6 @@ function reset() { notes.length = 0; buttons.length = 0; $("#note_list").html(""); - $("#alias_matched").html(""); $("#consos_list").html(""); $("#user_note").text(""); $("#profile_pic").attr("src", "/media/pic/default.png"); @@ -167,7 +156,7 @@ function reset() { function consumeAll() { notes_display.forEach(function(note_display) { 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); }); }); diff --git a/static/js/transfer.js b/static/js/transfer.js index e5aafc39..78a8ca08 100644 --- a/static/js/transfer.js +++ b/static/js/transfer.js @@ -14,8 +14,6 @@ function reset() { dests.length = 0; $("#source_note_list").html(""); $("#dest_note_list").html(""); - $("#source_alias_matched").html(""); - $("#dest_alias_matched").html(""); $("#amount").val(""); $("#reason").val(""); $("#last_name").val(""); @@ -28,37 +26,110 @@ function reset() { } $(document).ready(function() { - autoCompleteNote("source_note", "source_alias_matched", "source_note_list", sources, sources_notes_display, - "source_alias", "source_note", "user_note", "profile_pic"); - autoCompleteNote("dest_note", "dest_alias_matched", "dest_note_list", dests, dests_notes_display, - "dest_alias", "dest_note", "user_note", "profile_pic", function() { - if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) { - let last = dests_notes_display[dests_notes_display.length - 1]; - dests_notes_display.length = 0; - dests_notes_display.push(last); + /** + * If we are in credit/debit mode, check that only one note is entered. + * More over, get first name and last name to autocomplete fields. + */ + function checkUniqueNote() { + if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) { + let arr = $("#type_credit").is(":checked") ? dests_notes_display : sources_notes_display; - last.quantity = 1; + if (arr.length === 0) + return; - 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) { - $("#last_name").val(user.last_name); - $("#first_name").val(user.first_name); - }); - }); - } - else { + let last = arr[arr.length - 1]; + arr.length = 0; + arr.push(last); + + 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) { $("#last_name").val(user.last_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; + } + 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. let type_gift = $("#type_gift"); // Default mode @@ -91,7 +162,7 @@ $("#btn_transfer").click(function() { "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "resourcetype": "Transaction", "source": user_id, - "destination": dest.id, + "destination": dest.note.id, "destination_alias": dest.name }).done(function () { addMsg("Le transfert de " @@ -99,7 +170,7 @@ $("#btn_transfer").click(function() { + " vers la note " + dest.name + " a été fait avec succès !", "success"); reset(); - }).fail(function () { + }).fail(function () { // do it again but valid = false $.post("/api/note/transaction/transaction/", { "csrfmiddlewaretoken": CSRF_TOKEN, @@ -111,7 +182,7 @@ $("#btn_transfer").click(function() { "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "resourcetype": "Transaction", "source": user_id, - "destination": dest.id, + "destination": dest.note.id, "destination_alias": dest.name }).done(function () { addMsg("Le transfert de " @@ -141,9 +212,9 @@ $("#btn_transfer").click(function() { "valid": true, "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "resourcetype": "Transaction", - "source": source.id, + "source": source.note.id, "source_alias": source.name, - "destination": dest.id, + "destination": dest.note.id, "destination_alias": dest.name }).done(function () { addMsg("Le transfert de " @@ -151,7 +222,7 @@ $("#btn_transfer").click(function() { + " vers la note " + dest.name + " a été fait avec succès !", "success"); reset(); - }).fail(function (err) { + }).fail(function (err) { // do it again but valid = false $.post("/api/note/transaction/transaction/", { "csrfmiddlewaretoken": CSRF_TOKEN, @@ -162,9 +233,9 @@ $("#btn_transfer").click(function() { "invalidity_reason": "Solde insuffisant", "polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE, "resourcetype": "Transaction", - "source": source.id, + "source": source.note.id, "source_alias": source.name, - "destination": dest.id, + "destination": dest.note.id, "destination_alias": dest.name }).done(function () { addMsg("Le transfert de " @@ -184,10 +255,11 @@ $("#btn_transfer").click(function() { }); } else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) { let special_note = $("#credit_type").val(); - let user_note = dests_notes_display[0].id; + let user_note; let given_reason = $("#reason").val(); let source, dest, reason; if ($("#type_credit").is(':checked')) { + user_note = dests_notes_display[0].note.id; source = special_note; dest = user_note; reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase(); @@ -195,6 +267,7 @@ $("#btn_transfer").click(function() { reason += " (" + given_reason + ")"; } else { + user_note = sources_notes_display[0].note.id; source = user_note; dest = special_note; reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase(); @@ -225,4 +298,4 @@ $("#btn_transfer").click(function() { reset(); }); } -}); \ No newline at end of file +}); diff --git a/templates/base.html b/templates/base.html index 646f27e9..ddc2398f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -58,6 +58,20 @@ SPDX-License-Identifier: GPL-3.0-or-later cursor: pointer; 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); + } {% block extracss %}{% endblock %} diff --git a/templates/note/conso_form.html b/templates/note/conso_form.html index b108a96f..451d8a27 100644 --- a/templates/note/conso_form.html +++ b/templates/note/conso_form.html @@ -10,10 +10,10 @@
    {# User details column #} -
    -
    +
    +
    + id="profile_pic" alt="" class="card-img-top">
    @@ -25,38 +25,45 @@

    - {% trans "Select emitters" %} + {% trans "Consum" %}

    -
      -
    -
    - -
      +
      +
      + + {# User search with autocompletion #} +
    -
    +

    {% trans "Select consumptions" %}

    -
      -
    - +
    +
      +
    +
    +
    {# Buttons column #} -
    +
    {# Show last used buttons #}
    @@ -123,10 +130,12 @@
    @@ -146,7 +155,7 @@ {% endblock %} {% block extrajavascript %} - + {% endblock %} diff --git a/templates/note/transactiontemplate_form.html b/templates/note/transactiontemplate_form.html index 1f9a574a..e4bc42a7 100644 --- a/templates/note/transactiontemplate_form.html +++ b/templates/note/transactiontemplate_form.html @@ -1,12 +1,28 @@ {% extends "base.html" %} + {% load static %} {% load i18n %} {% load crispy_forms_tags %} +{% load pretty_money %} + {% block content %} -

    {% trans "Buttons list" %}

    -
    -{% csrf_token %} -{{form|crispy}} - -
    +

    + {% trans "Buttons list" %} +

    +
    + {% csrf_token %} + {{form|crispy}} + +
    + + {% if price_history and price_history.1 %} +
    + +

    {% trans "Price history" %}

    +
      + {% for price in price_history %} +
    • {{ price.price|pretty_money }} {% if price.time %}({% trans "Obsolete since" %} {{ price.time }}){% else %}({% trans "Current price" %}){% endif %}
    • + {% endfor %} +
    + {% endif %} {% endblock %} diff --git a/templates/note/transactiontemplate_list.html b/templates/note/transactiontemplate_list.html index cf9bc5ed..280f9faf 100644 --- a/templates/note/transactiontemplate_list.html +++ b/templates/note/transactiontemplate_list.html @@ -6,9 +6,17 @@

    - {% trans "search button" %} + {% trans "Search button" %}

    - + +
    +
    + +
    +

    {% trans "New button" %}
    @@ -29,50 +37,65 @@ {% block extrajavascript %} {% endblock %}