mirror of https://gitlab.crans.org/bde/nk20
Better transfers
This commit is contained in:
parent
5782e91aa8
commit
7b69ffdefe
|
@ -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,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Reference in New Issue