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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -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 <li> entry with a given id and text
*/
function li(id, text) {
return "<li class=\"list-group-item py-1 d-flex justify-content-between align-items-center\"" +
" id=\"" + id + "\">" + text + "</li>\n";
function li(id, text, extra_css) {
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";
}
/**
* 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
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>");
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>",
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 = '<ul class="list-group list-group-flush">';
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 += '</ul>';
// 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
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>");
});
// 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
+ "<span class=\"badge badge-dark badge-pill\">"
+ disp.quantity + "</span>",
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("<i class='fa fa-spinner'></i>");
// 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");

View File

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

View File

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

View File

@ -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);
}
</style>
{% block extracss %}{% endblock %}

View File

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

View File

@ -38,8 +38,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div>
<div class="row">
<div class="col-xl-4" id="note_infos_div">
<div class="col-md-3" id="note_infos_div">
<div class="card border-success shadow mb-4">
<img src="/media/pic/default.png"
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 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-header">
<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>
<div class="card-body">
<input class="form-control mx-auto d-block" type="text" id="source_note" />
<ul class="list-group list-group-flush" id="source_alias_matched">
<input class="form-control mx-auto d-block" type="text" id="source_note" placeholder="{% trans "Name or alias..." %}" />
</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>
</div>
</div>
</div>
{% if "note.notespecial"|not_empty_model_list %}
<div class="col-md-4" id="external_div" style="display: none;">
<div class="card border-success shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "External payment" %}
</p>
<div class="col-md-3" id="external_div">
<div class="card border-warning shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "Action" %}
</p>
</div>
<ul class="list-group list-group-flush" id="source_note_list">
</ul>
<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>
<ul class="list-group list-group-flush" id="source_note_list">
</ul>
<div class="card-body">
<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="col-md-12">
<label for="credit_type">{% trans "Transfer type" %} :</label>
@ -105,44 +134,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div>
</div>
</div>
<hr>
<div class="form-row">
<div class="col-md-12">
<button id="btn_transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button>
</div>
</div>
</div>
</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="col-md-12">
<button id="btn_transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button>
</div>
</div>
@ -161,34 +160,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }};
SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }};
user_id = {{ user.note.pk }};
$("#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" %}");
});
username = "{{ user.username }}";
</script>
<script src="/static/js/transfer.js"></script>
{% endblock %}

View File

@ -1,12 +1,28 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load pretty_money %}
{% block content %}
<p><a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Buttons list" %}</a></p>
<form method="post">
{% csrf_token %}
{{form|crispy}}
<button class="btn btn-primary" type="submit">Submit</button>
</form>
<p>
<a class="btn btn-default" href="{% url 'note:template_list' %}">{% trans "Buttons list" %}</a>
</p>
<form method="post">
{% csrf_token %}
{{form|crispy}}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</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 %}

View File

@ -6,9 +6,17 @@
<div class="row justify-content-center mb-4">
<div class="col-md-10 text-center">
<h4>
{% trans "search button" %}
{% trans "Search button" %}
</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>
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}">{% trans "New button" %}</a>
</div>
@ -29,50 +37,65 @@
{% block extrajavascript %}
<script>
/* fonction appelée à la fin du timer */
function getInfo() {
var asked = $("#search_field").val();
/* on ne fait la requête que si on a au moins un caractère pour chercher */
var sel = $(".table-row");
if (asked.length >= 1) {
$.getJSON("/api/note/transaction/template/?format=json&search="+asked, function(buttons){
let selected_id = buttons.results.map((a => "#row-"+a.id));
$(".table-row,"+selected_id.join()).show();
$(".table-row").not(selected_id.join()).hide();
/* fonction appelée à la fin du timer */
function getInfo() {
var asked = $("#search_field").val();
/* on ne fait la requête que si on a au moins un caractère pour chercher */
if (asked.length >= 1) {
$.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));
console.log(".table-row " + selected_id.join());
$(".table-row " + selected_id.join()).removeClass('d-none');
$(".table-row").not(selected_id.join()).addClass('d-none');
});
}
else {
if ($("#active_only").is(":checked")) {
$('.table-success').removeClass('d-none');
$('.table-danger').addClass('d-none');
}
else {
// show everything
$('table tr').removeClass('d-none');
}
}
}
});
}else{
// show everything
$('table tr').show();
var timer;
var timer_on;
/* Fontion appelée quand le texte change (délenche le timer) */
function search_field_moved() {
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
clearTimeout(timer);
timer = setTimeout(getInfo, 300);
}
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
timer = setTimeout(getInfo, 300);
timer_on = true;
}
}
}
var timer;
var timer_on;
/* Fontion appelée quand le texte change (délenche le timer) */
function search_field_moved(secondfield) {
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
clearTimeout(timer);
timer = setTimeout("getInfo(" + secondfield + ")", 300);
}
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
timer = setTimeout("getInfo(" + secondfield + ")", 300);
timer_on = true;
}
}
// on click of button "delete" , call the API
function delete_button(button_id){
$.ajax({
url:"/api/note/transaction/template/"+button_id+"/",
method:"DELETE",
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
})
.done(function(){
addMsg('{% trans "button successfully deleted "%}','success');
$("#buttons_table").load("{% url 'note:template_list' %} #buttons_table");
})
.fail(function(){
addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' )
});
}
// on click of button "delete" , call the API
function delete_button(button_id) {
$.ajax({
url:"/api/note/transaction/template/"+button_id+"/",
method:"DELETE",
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
})
.done(function(){
addMsg('{% trans "button successfully deleted "%}','success');
$("#buttons_table").load("{% url 'note:template_list' %} #buttons_table");
})
.fail(function(){
addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' )
});
}
$(document).ready(function() {
$("#search_field").keyup(search_field_moved);
$("#active_only").change(search_field_moved);
search_field_moved();
});
</script>
{% endblock %}