diff --git a/apps/note/forms.py b/apps/note/forms.py index 2e8e44561d532c2f539b631ff1886d97a3cf6f58..933e29f5f21984d679a09996fa1312af36af4414 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -50,52 +50,3 @@ class TransactionTemplateForm(forms.ModelForm): }, ), } - - -class TransactionForm(forms.ModelForm): - def save(self, commit=True): - super().save(commit) - - def clean(self): - """ - If the user has no right to transfer funds, then it will be the source of the transfer by default. - Transactions between a note and the same note are not authorized. - """ - - cleaned_data = super().clean() - if "source" not in cleaned_data: # TODO Replace it with "if %user has no right to transfer funds" - cleaned_data["source"] = self.user.note - - if cleaned_data["source"].pk == cleaned_data["destination"].pk: - self.add_error("destination", _("Source and destination must be different.")) - - return cleaned_data - - class Meta: - model = Transaction - fields = ( - 'source', - 'destination', - 'reason', - 'amount', - ) - - # Voir ci-dessus - widgets = { - 'source': - autocomplete.ModelSelect2( - url='note:note_autocomplete', - attrs={ - 'data-placeholder': 'Note ...', - 'data-minimum-input-length': 1, - }, - ), - 'destination': - autocomplete.ModelSelect2( - url='note:note_autocomplete', - attrs={ - 'data-placeholder': 'Note ...', - 'data-minimum-input-length': 1, - }, - ), - } diff --git a/apps/note/views.py b/apps/note/views.py index f950fd7312d585c05b8c05e92ffcfa2634f26cab..e323a4fdc8833aaa3947ccde169c36529ba92564 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -5,24 +5,22 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.db.models import Q -from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, UpdateView +from django.views.generic import CreateView, ListView, UpdateView, TemplateView from django_tables2 import SingleTableView -from .forms import TransactionForm, TransactionTemplateForm +from .forms import TransactionTemplateForm from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction from .tables import HistoryTable -class TransactionCreate(LoginRequiredMixin, CreateView): +class TransactionCreate(LoginRequiredMixin, TemplateView): """ Show transfer page TODO: If user have sufficient rights, they can transfer from an other note """ - model = Transaction - form_class = TransactionForm + template_name = "note/transaction_form.html" def get_context_data(self, **kwargs): """ @@ -31,26 +29,10 @@ class TransactionCreate(LoginRequiredMixin, CreateView): context = super().get_context_data(**kwargs) context['title'] = _('Transfer money from your account ' 'to one or others') - - context['no_cache'] = True + context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk return context - def get_form(self, form_class=None): - """ - If the user has no right to transfer funds, then it won't have the choice of the source of the transfer. - """ - form = super().get_form(form_class) - - if False: # TODO: fix it with "if %user has no right to transfer funds" - del form.fields['source'] - form.user = self.request.user - - return form - - def get_success_url(self): - return reverse('note:transfer') - class NoteAutocomplete(autocomplete.Select2QuerySetView): """ diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 05836a54d16fd04122280d387ccd777d9024271c..6fb317ad1161de48e4bd531c9e016c9a1a3194b3 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -408,6 +408,10 @@ msgstr "Un modèle de transaction avec un nom similaire existe déjà ." msgid "amount" msgstr "montant" +#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:109 +msgid "Amount" +msgstr "Montant" + #: apps/note/models/transactions.py:57 msgid "in centimes" msgstr "en centimes" @@ -428,6 +432,9 @@ msgstr "quantité" msgid "reason" msgstr "raison" +msgid "Reason" +msgstr "Raison" + #: apps/note/models/transactions.py:115 msgid "valid" msgstr "valide" diff --git a/static/js/base.js b/static/js/base.js index 0c63fb26deff85c7966160474ebaf4507d98ce73..cfd8f70e41952af0cc68e982b222d88d48d57fbf 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -26,7 +26,8 @@ function pretty_money(value) { if (value % 100 === 0) return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + " €"; else - return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "." + (Math.abs(value) % 100) + " €"; + return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "." + + (Math.abs(value) % 100 < 10 ? "0" : "") + (Math.abs(value) % 100) + " €"; } /** @@ -47,7 +48,6 @@ function getMatchedNotes(pattern, fun) { aliases.results.forEach(function(alias) { getJSONSync("/api/note/note/" + alias.note + "/?format=json", function (note) { fun(note, alias); - console.log(alias.name); }); }); }); @@ -85,13 +85,14 @@ function displayNote(note, alias, user_note_field=null, profile_pic_field=null) * @param d The note to remove * @param note_prefix The prefix of the identifiers of the <li> blocks of the emitters * @param notes_display An array containing the infos of the buyers: [alias, note id, note object, quantity] + * @param note_list_id The div block identifier where the notes of the buyers are displayed * @param user_note_field The identifier of the field that display the note of the hovered note (useful in * consumptions, put null if not used) * @param profile_pic_field The identifier of the field that display the profile picture of the hovered note * (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, user_note_field=null, profile_pic_field=null) { +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 = ""; @@ -103,15 +104,20 @@ function removeNote(d, note_prefix="note", notes_display, user_note_field=null, + "<span class=\"badge badge-dark badge-pill\">" + disp[3] + "</span>"); } }); - $("#note_list").html(html); + + notes_display.length = 0; + new_notes_display.forEach(function(disp) { + notes_display.push(disp); + }); + + $("#" + note_list_id).html(html); notes_display.forEach(function (disp) { - obj = $("#" + note_prefix + "_" + disp[1]); - obj.click(removeNote(disp, note_prefix, notes_display, user_note_field, profile_pic_field)); + let obj = $("#" + note_prefix + "_" + disp[1]); + obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field)); obj.hover(function() { displayNote(disp[2], disp[0], user_note_field, profile_pic_field); }); }); - notes_display = new_notes_display; }); } @@ -155,7 +161,7 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes let aliases_matched_html = ""; // Get matched notes with the given pattern getMatchedNotes(pattern, function(note, alias) { - aliases_matched_html += li("alias_" + alias.normalized_name, alias.name); + aliases_matched_html += li(alias_prefix + "_" + alias.normalized_name, alias.name); note.alias = alias; notes.push(note); }); @@ -189,7 +195,7 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes let note_list = $("#" + note_list_id); let html = ""; notes_display.forEach(function(disp) { - html += li("note_" + disp[1], disp[0] + html += li(note_prefix + "_" + disp[1], disp[0] + "<span class=\"badge badge-dark badge-pill\">" + disp[3] + "</span>"); }); @@ -204,7 +210,7 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes }); // When an emitter is clicked, it is removed - line_obj.click(removeNote(disp, note_prefix, notes_display, user_note_field, profile_pic_field)); + line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field)); }); }); }); diff --git a/templates/base.html b/templates/base.html index 2f2f4ab4fb7ad5aff902e36a49b0fd1ed8cf24e6..6af81fe8049f6866616730e2ec1a38189e45c06e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -117,6 +117,7 @@ SPDX-License-Identifier: GPL-3.0-or-later </nav> <div class="container-fluid my-3" style="max-width: 1600px;"> {% block contenttitle %}<h1>{{ title }}</h1>{% endblock %} + <div id="messages"></div> {% block content %} <p>Default content...</p> {% endblock content %} diff --git a/templates/note/conso_form.html b/templates/note/conso_form.html index 5cfccb375c899edbb49ccdb2ccbb47fa98d8a1d0..c5dcef009d98c8ddf138a9cf9c5b214d7d1223dc 100644 --- a/templates/note/conso_form.html +++ b/templates/note/conso_form.html @@ -130,7 +130,7 @@ {% block extrajavascript %} <script type="text/javascript" src="/static/js/consos.js"></script> <script type="text/javascript"> - let CSRF_TOKEN = "{{ csrf_token }}"; + var CSRF_TOKEN = "{{ csrf_token }}"; {% for button in transaction_templates %} {% if button.display %} diff --git a/templates/note/transaction_form.html b/templates/note/transaction_form.html index ff8504bc157666e021a4698c430db81163ebcd28..842d81522446dd3f6d4e8444f2d8dd95036e1aca 100644 --- a/templates/note/transaction_form.html +++ b/templates/note/transaction_form.html @@ -6,32 +6,133 @@ SPDX-License-Identifier: GPL-2.0-or-later {% load i18n static %} {% block content %} - <form method="post" onsubmit="window.onbeforeunload=null">{% csrf_token %} - {% if form.non_field_errors %} - <p class="errornote"> - {% for error in form.non_field_errors %} - {{ error }} - {% endfor %} - </p> - {% endif %} - <fieldset class="module aligned"> - {% for field in form %} - <div class="form-row{% if field.errors %} errors{% endif %}"> - {{ field.errors }} - <div> - {{ field.label_tag }} - {% if field.is_readonly %} - <div class="readonly">{{ field.contents }}</div> - {% else %} - {{ field }} - {% endif %} - {% if field.field.help_text %} - <div class="help">{{ field.field.help_text|safe }}</div> - {% endif %} - </div> + <div class="row"> + <div class="col-md-6"> + <div class="card border-success shadow mb-4"> + <div class="card-header"> + <p class="card-text font-weight-bold"> + Sélection des émetteurs + </p> </div> - {% endfor %} - </fieldset> - <input type="submit" value="{% trans 'Transfer' %}"> - </form> + <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"> + </ul> + </div> + </div> + </div> + + <div class="col-md-6"> + <div class="card border-info shadow mb-4"> + <div class="card-header"> + <p class="card-text font-weight-bold"> + Sélection des destinataires + </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> + <input class="form-control mx-auto d-block" type="number" min="-20" id="amount" /> + </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" /> + </div> + </div> + + <div class="form-row"> + <div class="col-md-12"> + <button id="transfer" class="form-control btn btn-primary">{% trans 'Transfer' %}</button> + </div> + </div> +{% endblock %} + +{% block extrajavascript %} + <script> + var CSRF_TOKEN = "{{ csrf_token }}"; + + var sources = []; + var sources_notes_display = []; + var dests = []; + var dests_notes_display = []; + + $(document).ready(function() { + autoCompleteNote("source_note", "source_alias_matched", "source_note_list", sources, sources_notes_display, + "source_alias", "source_note"); + autoCompleteNote("dest_note", "dest_alias_matched", "dest_note_list", dests, dests_notes_display, + "dest_alias", "dest_note"); + }); + + $("#transfer").click(function() { + sources_notes_display.forEach(function(source) { + dests_notes_display.forEach(function(dest) { + $.post("/api/note/transaction/transaction/", + { + "csrfmiddlewaretoken": CSRF_TOKEN, + "quantity": source[3] * dest[3], + "amount": $("#amount").val(), + "reason": $("#reason").val() + " (Transfert)", + "valid": true, + "polymorphic_ctype": {{ polymorphic_ctype }}, + "resourcetype": "Transaction", + "source": source[1], + "destination": dest[1] + }, function() { + sources_notes_display.length = 0; + sources.length = 0; + dests_notes_display.length = 0; + dests.length = 0; + $("#source_note_list").html(""); + $("#dest_note_list").html(""); + $("#source_alias_matched").html(""); + $("#dest_alias_matched").html(""); + $("#amount").val(""); + $("#reason").val(""); + refreshBalance(); + + let msgDiv = $("#messages"); + let html = msgDiv.html(); + html += "<div class=\"alert alert-success\">Le transfert de " + + pretty_money(source[3] * dest[3] * $("#amount").val()) + " de la note " + source[0] + + " vers la note " + dest[0] + " a été fait avec succès !</div>\n"; + msgDiv.html(html); + }).fail(function (err) { + sources_notes_display.length = 0; + sources.length = 0; + dests_notes_display.length = 0; + dests.length = 0; + $("#source_note_list").html(""); + $("#dest_note_list").html(""); + $("#source_alias_matched").html(""); + $("#dest_alias_matched").html(""); + $("#amount").val(""); + $("#reason").val(""); + refreshBalance(); + + let msgDiv = $("#messages"); + let html = msgDiv.html(); + html += "<div class=\"alert alert-danger\">Le transfert de " + + pretty_money(source[3] * dest[3] * $("#amount").val()) + " de la note " + source[0] + + " vers la note " + dest[0] + " a échoué : " + err.responseText + "</div>\n"; + msgDiv.html(html); + }); + }); + }); + }); + </script> {% endblock %}