Better transfers

This commit is contained in:
Yohann D'ANELLO 2020-03-12 23:12:49 +01:00
parent 5782e91aa8
commit 7b69ffdefe
7 changed files with 158 additions and 110 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}