Merge branch 'Aufinal/transferts_historique' into 'master'

Rajoute les transferts dans l'historique

Closes #77 and #233

See merge request klub-dev-ens/gestioCOF!399
This commit is contained in:
Antonin Reitz 2020-04-23 18:46:30 +02:00
commit 922190d20f
9 changed files with 530 additions and 571 deletions

View file

@ -20,7 +20,7 @@
z-index:10; z-index:10;
} }
#history .opegroup { #history .group {
height:30px; height:30px;
line-height:30px; line-height:30px;
background-color: #c63b52; background-color: #c63b52;
@ -30,29 +30,29 @@
overflow:auto; overflow:auto;
} }
#history .opegroup .time { #history .group .time {
width:70px; width:70px;
} }
#history .opegroup .trigramme { #history .group .trigramme {
width:55px; width:55px;
text-align:right; text-align:right;
} }
#history .opegroup .amount { #history .group .amount {
text-align:right; text-align:right;
width:90px; width:90px;
} }
#history .opegroup .valid_by { #history .group .valid_by {
padding-left:20px padding-left:20px
} }
#history .opegroup .comment { #history .group .comment {
padding-left:20px; padding-left:20px;
} }
#history .ope { #history .entry {
position:relative; position:relative;
height:25px; height:25px;
line-height:24px; line-height:24px;
@ -61,38 +61,38 @@
overflow:auto; overflow:auto;
} }
#history .ope .amount { #history .entry .amount {
width:50px; width:50px;
text-align:right; text-align:right;
} }
#history .ope .infos1 { #history .entry .infos1 {
width:80px; width:80px;
text-align:right; text-align:right;
} }
#history .ope .infos2 { #history .entry .infos2 {
padding-left:15px; padding-left:15px;
} }
#history .ope .addcost { #history .entry .addcost {
padding-left:20px; padding-left:20px;
} }
#history .ope .canceled { #history .entry .canceled {
padding-left:20px; padding-left:20px;
} }
#history div.ope.ui-selected, #history div.ope.ui-selecting { #history div.entry.ui-selected, #history div.entry.ui-selecting {
background-color:rgba(200,16,46,0.6); background-color:rgba(200,16,46,0.6);
color:#FFF; color:#FFF;
} }
#history .ope.canceled, #history .transfer.canceled { #history .entry.canceled {
color:#444; color:#444;
} }
#history .ope.canceled::before, #history.transfer.canceled::before { #history .entry.canceled::before {
position: absolute; position: absolute;
content: ' '; content: ' ';
width:100%; width:100%;
@ -101,10 +101,11 @@
border-top: 1px solid rgba(200,16,46,0.5); border-top: 1px solid rgba(200,16,46,0.5);
} }
#history .transfer .amount { #history .group .infos {
width:80px; text-align:center;
width:145px;
} }
#history .transfer .from_acc { #history .entry .glyphicon {
padding-left:10px; padding-left:15px;
} }

View file

@ -2,31 +2,59 @@ function dateUTCToParis(date) {
return moment.tz(date, 'UTC').tz('Europe/Paris'); return moment.tz(date, 'UTC').tz('Europe/Paris');
} }
// TODO : classifier (later)
function KHistory(options = {}) { function KHistory(options = {}) {
$.extend(this, KHistory.default_options, options); $.extend(this, KHistory.default_options, options);
this.$container = $(this.container); this.$container = $(this.container);
this.$container.selectable({
filter: 'div.group, div.entry',
selected: function (e, ui) {
$(ui.selected).each(function () {
if ($(this).hasClass('group')) {
var id = $(this).data('id');
$(this).siblings('.entry').filter(function () {
return $(this).data('group_id') == id
}).addClass('ui-selected');
}
});
},
});
this.reset = function () { this.reset = function () {
this.$container.html(''); this.$container.html('');
}; };
this.addOpeGroup = function (opegroup) { this.add_history_group = function (group) {
var $day = this._getOrCreateDay(opegroup['at']); var $day = this._get_or_create_day(group['at']);
var $opegroup = this._opeGroupHtml(opegroup); var $group = this._group_html(group);
$day.after($opegroup); $day.after($group);
var trigramme = opegroup['on_acc_trigramme']; var trigramme = group['on_acc_trigramme'];
var is_cof = opegroup['is_cof']; var is_cof = group['is_cof'];
for (var i = 0; i < opegroup['opes'].length; i++) { var type = group['type']
var $ope = this._opeHtml(opegroup['opes'][i], is_cof, trigramme); // TODO : simplifier ça ?
$ope.data('opegroup', opegroup['id']); switch (type) {
$opegroup.after($ope); case 'operation':
for (let ope of group['entries']) {
var $ope = this._ope_html(ope, is_cof, trigramme);
$ope.data('group_id', group['id']);
$group.after($ope);
}
break;
case 'transfer':
for (let transfer of group['entries']) {
var $transfer = this._transfer_html(transfer);
$transfer.data('group_id', group['id']);
$group.after($transfer);
}
break;
} }
} }
this._opeHtml = function (ope, is_cof, trigramme) { this._ope_html = function (ope, is_cof, trigramme) {
var $ope_html = $(this.template_ope); var $ope_html = $(this.template_ope);
var parsed_amount = parseFloat(ope['amount']); var parsed_amount = parseFloat(ope['amount']);
var amount = amountDisplay(parsed_amount, is_cof, trigramme); var amount = amountDisplay(parsed_amount, is_cof, trigramme);
@ -54,7 +82,8 @@ function KHistory(options = {}) {
} }
$ope_html $ope_html
.data('ope', ope['id']) .data('type', 'operation')
.data('id', ope['id'])
.find('.amount').text(amount).end() .find('.amount').text(amount).end()
.find('.infos1').text(infos1).end() .find('.infos1').text(infos1).end()
.find('.infos2').text(infos2).end(); .find('.infos2').text(infos2).end();
@ -62,54 +91,89 @@ function KHistory(options = {}) {
var addcost_for = ope['addcost_for__trigramme']; var addcost_for = ope['addcost_for__trigramme'];
if (addcost_for) { if (addcost_for) {
var addcost_amount = parseFloat(ope['addcost_amount']); var addcost_amount = parseFloat(ope['addcost_amount']);
$ope_html.find('.addcost').text('(' + amountDisplay(addcost_amount, is_cof) + 'UKF pour ' + addcost_for + ')'); $ope_html.find('.addcost').text('(' + amountDisplay(addcost_amount, is_cof) + ' UKF pour ' + addcost_for + ')');
} }
if (ope['canceled_at']) if (ope['canceled_at'])
this.cancelOpe(ope, $ope_html); this.cancel_entry(ope, $ope_html);
return $ope_html; return $ope_html;
} }
this.cancelOpe = function (ope, $ope = null) { this._transfer_html = function (transfer) {
if (!$ope) var $transfer_html = $(this.template_transfer);
$ope = this.findOpe(ope['id']); var parsed_amount = parseFloat(transfer['amount']);
var amount = parsed_amount.toFixed(2) + '€';
var cancel = 'Annulé'; $transfer_html
var canceled_at = dateUTCToParis(ope['canceled_at']); .data('type', 'transfer')
if (ope['canceled_by__trigramme']) .data('id', transfer['id'])
cancel += ' par ' + ope['canceled_by__trigramme']; .find('.amount').text(amount).end()
cancel += ' le ' + canceled_at.format('DD/MM/YY à HH:mm:ss'); .find('.infos1').text(transfer['from_acc']).end()
.find('.infos2').text(transfer['to_acc']).end();
$ope.addClass('canceled').find('.canceled').text(cancel); if (transfer['canceled_at'])
this.cancel_entry(transfer, $transfer_html);
return $transfer_html;
} }
this._opeGroupHtml = function (opegroup) {
var $opegroup_html = $(this.template_opegroup);
var at = dateUTCToParis(opegroup['at']).format('HH:mm:ss'); this.cancel_entry = function (entry, $entry = null) {
var trigramme = opegroup['on_acc__trigramme']; if (!$entry)
var amount = amountDisplay( $entry = this.find_entry(entry["id"], entry["type"]);
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
var comment = opegroup['comment'] || '';
$opegroup_html var cancel = 'Annulé';
.data('opegroup', opegroup['id']) var canceled_at = dateUTCToParis(entry['canceled_at']);
if (entry['canceled_by__trigramme'])
cancel += ' par ' + entry['canceled_by__trigramme'];
cancel += ' le ' + canceled_at.format('DD/MM/YY à HH:mm:ss');
$entry.addClass('canceled').find('.canceled').text(cancel);
}
this._group_html = function (group) {
var type = group['type'];
switch (type) {
case 'operation':
var $group_html = $(this.template_opegroup);
var trigramme = group['on_acc__trigramme'];
var amount = amountDisplay(
parseFloat(group['amount']), group['is_cof'], trigramme);
break;
case 'transfer':
var $group_html = $(this.template_transfergroup);
$group_html.find('.infos').text('Transferts').end()
var trigramme = '';
var amount = '';
break;
}
var at = dateUTCToParis(group['at']).format('HH:mm:ss');
var comment = group['comment'] || '';
$group_html
.data('type', type)
.data('id', group['id'])
.find('.time').text(at).end() .find('.time').text(at).end()
.find('.amount').text(amount).end() .find('.amount').text(amount).end()
.find('.comment').text(comment).end() .find('.comment').text(comment).end()
.find('.trigramme').text(trigramme).end(); .find('.trigramme').text(trigramme).end();
if (!this.display_trigramme) if (!this.display_trigramme)
$opegroup_html.find('.trigramme').remove(); $group_html.find('.trigramme').remove();
$group_html.find('.info').remove();
if (opegroup['valid_by__trigramme']) if (group['valid_by__trigramme'])
$opegroup_html.find('.valid_by').text('Par ' + opegroup['valid_by__trigramme']); $group_html.find('.valid_by').text('Par ' + group['valid_by__trigramme']);
return $opegroup_html; return $group_html;
} }
this._getOrCreateDay = function (date) { this._get_or_create_day = function (date) {
var at = dateUTCToParis(date); var at = dateUTCToParis(date);
var at_ser = at.format('YYYY-MM-DD'); var at_ser = at.format('YYYY-MM-DD');
var $day = this.$container.find('.day').filter(function () { var $day = this.$container.find('.day').filter(function () {
@ -118,35 +182,123 @@ function KHistory(options = {}) {
if ($day.length == 1) if ($day.length == 1)
return $day; return $day;
var $day = $(this.template_day).prependTo(this.$container); var $day = $(this.template_day).prependTo(this.$container);
return $day.data('date', at_ser).text(at.format('D MMMM')); return $day.data('date', at_ser).text(at.format('D MMMM YYYY'));
} }
this.findOpeGroup = function (id) { this.find_group = function (id, type = "operation") {
return this.$container.find('.opegroup').filter(function () { return this.$container.find('.group').filter(function () {
return $(this).data('opegroup') == id return ($(this).data('id') == id && $(this).data("type") == type)
}); });
} }
this.findOpe = function (id) { this.find_entry = function (id, type = 'operation') {
return this.$container.find('.ope').filter(function () { return this.$container.find('.entry').filter(function () {
return $(this).data('ope') == id return ($(this).data('id') == id && $(this).data('type') == type)
}); });
} }
this.cancelOpeGroup = function (opegroup) { this.update_opegroup = function (group, type = "operation") {
var $opegroup = this.findOpeGroup(opegroup['id']); var $group = this.find_group(group['id'], type);
var trigramme = $opegroup.find('.trigramme').text(); var trigramme = $group.find('.trigramme').text();
var amount = amountDisplay( var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme); parseFloat(group['amount']), group['is_cof'], trigramme);
$opegroup.find('.amount').text(amount); $group.find('.amount').text(amount);
} }
this.fetch = function (fetch_options) {
options = $.extend({}, this.fetch_options, fetch_options);
var that = this;
return $.ajax({
dataType: "json",
url: django_urls["kfet.history.json"](),
method: "POST",
data: options,
}).done(function (data) {
for (let group of data['groups']) {
that.add_history_group(group);
}
});
}
this._cancel = function (type, opes, password = "") {
if (window.lock == 1)
return false
window.lock = 1;
var that = this;
return $.ajax({
dataType: "json",
url: django_urls[`kfet.${type}s.cancel`](),
method: "POST",
data: opes,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
}).done(function (data) {
window.lock = 0;
that.$container.find('.ui-selected').removeClass('ui-selected');
for (let entry of data["canceled"]) {
entry["type"] = type;
that.cancel_entry(entry);
}
if (type == "operation") {
for (let opegroup of data["opegroups_to_update"]) {
that.update_opegroup(opegroup)
}
}
}).fail(function ($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function (password) {
this.cancel(opes, password);
});
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
window.lock = 0;
});
}
this.cancel_selected = function () {
var opes_to_cancel = {
"transfers": [],
"operations": [],
}
this.$container.find('.entry.ui-selected').each(function () {
type = $(this).data("type");
opes_to_cancel[`${type}s`].push($(this).data("id"));
});
if (opes_to_cancel["transfers"].length > 0 && opes_to_cancel["operations"].length > 0) {
// Lancer 2 requêtes AJAX et gérer tous les cas d'erreurs possibles est trop complexe
$.alert({
title: 'Erreur',
content: "Impossible de supprimer des transferts et des opérations en même temps !",
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
});
} else if (opes_to_cancel["transfers"].length > 0) {
delete opes_to_cancel["operations"];
this._cancel("transfer", opes_to_cancel);
} else if (opes_to_cancel["operations"].length > 0) {
delete opes_to_cancel["transfers"];
this._cancel("operation", opes_to_cancel);
}
}
} }
KHistory.default_options = { KHistory.default_options = {
container: '#history', container: '#history',
template_day: '<div class="day"></div>', template_day: '<div class="day"></div>',
template_opegroup: '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>', template_opegroup: '<div class="group"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
template_ope: '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>', template_transfergroup: '<div class="group"><span class="time"></span><span class="infos"></span><span class="valid_by"></span><span class="comment"></span></div>',
template_ope: '<div class="entry"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
template_transfer: '<div class="entry"><span class="amount"></span><span class="infos1"></span><span class="glyphicon glyphicon-arrow-right"></span><span class="infos2"></span><span class="canceled"></span></div>',
display_trigramme: true, display_trigramme: true,
} }

View file

@ -5,6 +5,7 @@
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
<script type="text/javascript" src="{% url 'js_reverse' %}" ></script>
<script type="text/javascript" src="{% static 'kfet/vendor/moment/moment.min.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/moment/moment.min.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/vendor/moment/fr.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/moment/fr.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script>
@ -81,7 +82,7 @@ $(document).ready(function() {
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
<div id="history" class="full"></div> <div id="history"></div>
</section> </section>
</div><!-- history tab --> </div><!-- history tab -->
@ -93,29 +94,22 @@ $(document).ready(function() {
khistory = new KHistory({ khistory = new KHistory({
display_trigramme: false, display_trigramme: false,
}); fetch_options: {
function getHistory() {
var data = {
'accounts': [{{ account.pk }}], 'accounts': [{{ account.pk }}],
} }
});
$.ajax({ $(document).on('keydown', function (e) {
dataType: "json", if (e.keyCode == 46) {
url : "{% url 'kfet.history.json' %}", // DEL (Suppr)
method : "POST", khistory.cancel_selected()
data : data, }
}) });
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) { khistory.fetch().done(function () {
khistory.addOpeGroup(data['opegroups'][i]);
}
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length; var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes); $('#nb_opes').text(nb_opes);
}); });
}
getHistory();
}); });
</script> </script>

View file

@ -5,6 +5,7 @@
<link rel="stylesheet" type="text/css" href="{% static 'kfet/vendor/multiple-select/multiple-select.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'kfet/vendor/multiple-select/multiple-select.css' %}">
<script type="text/javascript" src="{% static 'kfet/vendor/multiple-select/multiple-select.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/multiple-select/multiple-select.js' %}"></script>
{{ filter_form.media }} {{ filter_form.media }}
<script type="text/javascript" src="{% url 'js_reverse' %}" ></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script>
{% endblock %} {% endblock %}
@ -27,6 +28,9 @@
<li><b>Comptes</b> {{ filter_form.accounts }}</li> <li><b>Comptes</b> {{ filter_form.accounts }}</li>
</ul> </ul>
</div> </div>
<div class="buttons">
<button class="btn btn-primary" id="btn-fetch">Valider</button>
</div>
</aside> </aside>
{% endblock %} {% endblock %}
@ -40,6 +44,8 @@
$(document).ready(function() { $(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})} settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
window.lock = 0;
khistory = new KHistory(); khistory = new KHistory();
var $from_date = $('#id_from_date'); var $from_date = $('#id_from_date');
@ -67,17 +73,8 @@ $(document).ready(function() {
var accounts = getSelectedMultiple($accounts); var accounts = getSelectedMultiple($accounts);
data['accounts'] = accounts; data['accounts'] = accounts;
$.ajax({ khistory.fetch(data).done(function () {
dataType: "json", var nb_opes = khistory.$container.find('.entry:not(.canceled)').length;
url : "{% url 'kfet.history.json' %}",
method : "POST",
data : data,
})
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
khistory.addOpeGroup(data['opegroups'][i]);
}
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes); $('#nb_opes').text(nb_opes);
}); });
} }
@ -112,130 +109,17 @@ $(document).ready(function() {
countSelected: "# sur %" countSelected: "# sur %"
}); });
$("input").on('dp.change change', function() { $("#btn-fetch").on('click', function() {
khistory.reset(); khistory.reset();
getHistory(); getHistory();
}); });
khistory.$container.selectable({
filter: 'div.opegroup, div.ope',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
var opegroup = $(this).data('opegroup');
$(this).siblings('.ope').filter(function() {
return $(this).data('opegroup') == opegroup
}).addClass('ui-selected');
}
});
},
});
$(document).on('keydown', function (e) { $(document).on('keydown', function (e) {
if (e.keyCode == 46) { if (e.keyCode == 46) {
// DEL (Suppr) // DEL (Suppr)
var opes_to_cancel = []; khistory.cancel_selected()
khistory.$container.find('.ope.ui-selected').each(function () {
opes_to_cancel.push($(this).data('ope'));
});
if (opes_to_cancel.length > 0)
confirmCancel(opes_to_cancel);
} }
}); });
function confirmCancel(opes_to_cancel) {
var nb = opes_to_cancel.length;
var content = nb+" opérations vont être annulées";
$.confirm({
title: 'Confirmation',
content: content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
confirm: function() {
cancelOperations(opes_to_cancel);
}
});
}
function requestAuth(data, callback) {
var content = getErrorsHtml(data);
content += '<input type="password" name="password" autofocus>',
$.confirm({
title: 'Authentification requise',
content: content,
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirm: function() {
var password = this.$content.find('input').val();
callback(password);
},
onOpen: function() {
var that = this;
this.$content.find('input').on('keypress', function(e) {
if (e.keyCode == 13)
that.$confirmButton.click();
});
},
});
}
function getErrorsHtml(data) {
var content = '';
if ('missing_perms' in data['errors']) {
content += 'Permissions manquantes';
content += '<ul>';
for (var i=0; i<data['errors']['missing_perms'].length; i++)
content += '<li>'+data['errors']['missing_perms'][i]+'</li>';
content += '</ul>';
}
if ('negative' in data['errors']) {
var url_base = "{% url 'kfet.account.update' LIQ}";
url_base = base_url(0, url_base.length-8);
for (var i=0; i<data['errors']['negative'].length; i++) {
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
}
}
return content;
}
function cancelOperations(opes_array, password = '') {
var data = { 'operations' : opes_array }
$.ajax({
dataType: "json",
url : "{% url 'kfet.kpsul.cancel_operations' %}",
method : "POST",
data : data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
khistory.$container.find('.ui-selected').removeClass('ui-selected');
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelOperations(opes_array, password);
});
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
});
}
getHistory(); getHistory();
}); });
</script> </script>

View file

@ -189,7 +189,7 @@ $(document).ready(function() {
// ----- // -----
// Lock to avoid multiple requests // Lock to avoid multiple requests
lock = 0; window.lock = 0;
// Retrieve settings // Retrieve settings
@ -479,9 +479,9 @@ $(document).ready(function() {
var operations = $('#operation_formset'); var operations = $('#operation_formset');
function performOperations(password = '') { function performOperations(password = '') {
if (lock == 1) if (window.lock == 1)
return false; return false;
lock = 1; window.lock = 1;
var data = operationGroup.serialize() + '&' + operations.serialize(); var data = operationGroup.serialize() + '&' + operations.serialize();
$.ajax({ $.ajax({
dataType: "json", dataType: "json",
@ -497,7 +497,7 @@ $(document).ready(function() {
.done(function(data) { .done(function(data) {
updatePreviousOp(); updatePreviousOp();
coolReset(); coolReset();
lock = 0; window.lock = 0;
}) })
.fail(function($xhr) { .fail(function($xhr) {
var data = $xhr.responseJSON; var data = $xhr.responseJSON;
@ -513,7 +513,7 @@ $(document).ready(function() {
} }
break; break;
} }
lock = 0; window.lock = 0;
}); });
} }
@ -522,55 +522,6 @@ $(document).ready(function() {
performOperations(); performOperations();
}); });
// -----
// Cancel operations
// -----
var cancelButton = $('#cancel_operations');
var cancelForm = $('#cancel_form');
function cancelOperations(opes_array, password = '') {
if (lock == 1)
return false
lock = 1;
var data = { 'operations' : opes_array }
$.ajax({
dataType: "json",
url : "{% url 'kfet.kpsul.cancel_operations' %}",
method : "POST",
data : data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
coolReset();
lock = 0;
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelOperations(opes_array, password);
}, triInput);
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
lock = 0;
});
}
// Event listeners
cancelButton.on('click', function() {
cancelOperations();
});
// ----- // -----
// Articles data // Articles data
// ----- // -----
@ -1189,24 +1140,12 @@ $(document).ready(function() {
// History // History
// ----- // -----
khistory = new KHistory(); khistory = new KHistory({
fetch_options: {
function getHistory() {
var data = {
from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'), from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
}; opesonly: true,
$.ajax({ },
dataType: "json", });
url : "{% url 'kfet.history.json' %}",
method : "POST",
data : data,
})
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
khistory.addOpeGroup(data['opegroups'][i]);
}
});
}
var previousop_container = $('#previous_op'); var previousop_container = $('#previous_op');
@ -1302,29 +1241,10 @@ $(document).ready(function() {
// Cancel from history // Cancel from history
// ----- // -----
khistory.$container.selectable({
filter: 'div.opegroup, div.ope',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
var opegroup = $(this).data('opegroup');
$(this).siblings('.ope').filter(function() {
return $(this).data('opegroup') == opegroup
}).addClass('ui-selected');
}
});
},
});
$(document).on('keydown', function (e) { $(document).on('keydown', function (e) {
if (e.keyCode == 46) { if (e.keyCode == 46) {
// DEL (Suppr) // DEL (Suppr)
var opes_to_cancel = []; khistory.cancel_selected()
khistory.$container.find('.ope.ui-selected').each(function () {
opes_to_cancel.push($(this).data('ope'));
});
if (opes_to_cancel.length > 0)
cancelOperations(opes_to_cancel);
} }
}); });
@ -1333,16 +1253,9 @@ $(document).ready(function() {
// ----- // -----
OperationWebSocket.add_handler(function(data) { OperationWebSocket.add_handler(function(data) {
for (var i=0; i<data['opegroups'].length; i++) { for (var i=0; i<data['groups'].length; i++) {
if (data['opegroups'][i]['add']) { if (data['groups'][i]['add']) {
khistory.addOpeGroup(data['opegroups'][i]); khistory.add_history_group(data['groups'][i]);
} else if (data['opegroups'][i]['cancellation']) {
khistory.cancelOpeGroup(data['opegroups'][i]);
}
}
for (var i=0; i<data['opes'].length; i++) {
if (data['opes'][i]['cancellation']) {
khistory.cancelOpe(data['opes'][i]);
} }
} }
for (var i=0; i<data['checkouts'].length; i++) { for (var i=0; i<data['checkouts'].length; i++) {
@ -1396,7 +1309,7 @@ $(document).ready(function() {
khistory.reset(); khistory.reset();
resetSettings().done(function (){ resetSettings().done(function (){
getArticles(); getArticles();
getHistory(); khistory.fetch();
displayAddcost(); displayAddcost();
}); });
} }

View file

@ -1,9 +1,16 @@
{% extends 'kfet/base_col_2.html' %} {% extends 'kfet/base_col_2.html' %}
{% load staticfiles %} {% load staticfiles %}
{% load l10n staticfiles widget_tweaks %}
{% block title %}Transferts{% endblock %} {% block title %}Transferts{% endblock %}
{% block header-title %}Transferts{% endblock %} {% block header-title %}Transferts{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{% url 'js_reverse' %}" ></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script>
{% endblock %}
{% block fixed %} {% block fixed %}
<div class="buttons"> <div class="buttons">
@ -16,109 +23,31 @@
{% block main %} {% block main %}
<div id="history"> <table id="history" class="table">
{% for transfergroup in transfergroups %} </table>
<div class="opegroup transfergroup" data-transfergroup="{{ transfergroup.pk }}">
<span>{{ transfergroup.at }}</span>
<span>{{ transfergroup.valid_by.trigramme }}</span>
<span>{{ transfergroup.comment }}</span>
</div>
{% for transfer in transfergroup.transfers.all %}
<div class="ope transfer{% if transfer.canceled_at %} canceled{% endif %}" data-transfer="{{ transfer.pk }}" data-transfergroup="{{ transfergroup.pk }}">
<span class="amount">{{ transfer.amount }} €</span>
<span class="from_acc">{{ transfer.from_acc.trigramme }}</span>
<span class="glyphicon glyphicon-arrow-right"></span>
<span class="to_acc">{{ transfer.to_acc.trigramme }}</span>
</div>
{% endfor %}
{% endfor %}
</div>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
lock = 0; window.lock = 0;
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
function displayErrors(html) {
$.alert({
title: 'Erreurs',
content: html,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
});
}
function cancelTransfers(transfers_array, password = '') { var khistory = new KHistory({
if (lock == 1) fetch_options:{
return false transfersonly: true,
lock = 1; }
var data = { 'transfers' : transfers_array }
$.ajax({
dataType: "json",
url : "{% url 'kfet.transfers.cancel' %}",
method : "POST",
data : data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
for (var i=0; i<data['canceled'].length; i++) {
$('#history').find('.transfer[data-transfer='+data['canceled'][i]+']')
.addClass('canceled');
}
$('#history').find('.ui-selected').removeClass('ui-selected');
lock = 0;
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelTransfers(transfers_array, password);
});
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
lock = 0;
});
}
$('#history').selectable({
filter: 'div.transfergroup, div.transfer',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('transfergroup')) {
var transfergroup = $(this).attr('data-transfergroup');
$(this).siblings('.ope').filter(function() {
return $(this).attr('data-transfergroup') == transfergroup
}).addClass('ui-selected');
}
});
},
}); });
$(document).on('keydown', function (e) { $(document).on('keydown', function (e) {
if (e.keyCode == 46) { if (e.keyCode == 46) {
// DEL (Suppr) // DEL (Suppr)
var transfers_to_cancel = []; khistory.cancel_selected()
$('#history').find('.transfer.ui-selected').each(function () {
transfers_to_cancel.push($(this).attr('data-transfer'));
});
if (transfers_to_cancel.length > 0)
cancelTransfers(transfers_to_cancel);
} }
}); });
khistory.fetch()
}); });
</script> </script>

View file

@ -3,7 +3,7 @@ from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
from unittest import mock from unittest import mock
from django.contrib.auth.models import Group from django.contrib.auth.models import Group, User
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@ -1997,9 +1997,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with( self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [ "groups": [
{ {
"add": True, "add": True,
"type": "operation",
"at": mock.ANY, "at": mock.ANY,
"amount": Decimal("-5.00"), "amount": Decimal("-5.00"),
"checkout__name": "Checkout", "checkout__name": "Checkout",
@ -2008,7 +2009,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False, "is_cof": False,
"on_acc__trigramme": "000", "on_acc__trigramme": "000",
"valid_by__trigramme": None, "valid_by__trigramme": None,
"opes": [ "entries": [
{ {
"id": operation.pk, "id": operation.pk,
"addcost_amount": None, "addcost_amount": None,
@ -2269,9 +2270,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with( self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [ "groups": [
{ {
"add": True, "add": True,
"type": "operation",
"at": mock.ANY, "at": mock.ANY,
"amount": Decimal("10.75"), "amount": Decimal("10.75"),
"checkout__name": "Checkout", "checkout__name": "Checkout",
@ -2280,7 +2282,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False, "is_cof": False,
"on_acc__trigramme": "000", "on_acc__trigramme": "000",
"valid_by__trigramme": "100", "valid_by__trigramme": "100",
"opes": [ "entries": [
{ {
"id": operation.pk, "id": operation.pk,
"addcost_amount": None, "addcost_amount": None,
@ -2443,9 +2445,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with( self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [ "groups": [
{ {
"add": True, "add": True,
"type": "operation",
"at": mock.ANY, "at": mock.ANY,
"amount": Decimal("-10.75"), "amount": Decimal("-10.75"),
"checkout__name": "Checkout", "checkout__name": "Checkout",
@ -2454,7 +2457,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False, "is_cof": False,
"on_acc__trigramme": "000", "on_acc__trigramme": "000",
"valid_by__trigramme": None, "valid_by__trigramme": None,
"opes": [ "entries": [
{ {
"id": operation.pk, "id": operation.pk,
"addcost_amount": None, "addcost_amount": None,
@ -2601,9 +2604,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with( self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [ "groups": [
{ {
"add": True, "add": True,
"type": "operation",
"at": mock.ANY, "at": mock.ANY,
"amount": Decimal("10.75"), "amount": Decimal("10.75"),
"checkout__name": "Checkout", "checkout__name": "Checkout",
@ -2612,7 +2616,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False, "is_cof": False,
"on_acc__trigramme": "000", "on_acc__trigramme": "000",
"valid_by__trigramme": "100", "valid_by__trigramme": "100",
"opes": [ "entries": [
{ {
"id": operation.pk, "id": operation.pk,
"addcost_amount": None, "addcost_amount": None,
@ -2712,9 +2716,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db() self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00")) self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][ ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
0 "entries"
]["opes"][0] ][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00")) self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD") self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2752,9 +2756,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db() self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00")) self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][ ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
0 "entries"
]["opes"][0] ][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("0.80")) self.assertEqual(ws_data_ope["addcost_amount"], Decimal("0.80"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD") self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2790,9 +2794,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db() self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("106.00")) self.assertEqual(self.checkout.balance, Decimal("106.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][ ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
0 "entries"
]["opes"][0] ][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00")) self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD") self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2826,9 +2830,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db() self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("15.00")) self.assertEqual(self.accounts["addcost"].balance, Decimal("15.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][ ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
0 "entries"
]["opes"][0] ][0]
self.assertEqual(ws_data_ope["addcost_amount"], None) self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None) self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@ -2861,9 +2865,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db() self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("0.00")) self.assertEqual(self.accounts["addcost"].balance, Decimal("0.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][ ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
0 "entries"
]["opes"][0] ][0]
self.assertEqual(ws_data_ope["addcost_amount"], None) self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None) self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@ -3170,9 +3174,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with( self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [ "groups": [
{ {
"add": True, "add": True,
"type": "operation",
"at": mock.ANY, "at": mock.ANY,
"amount": Decimal("-9.00"), "amount": Decimal("-9.00"),
"checkout__name": "Checkout", "checkout__name": "Checkout",
@ -3181,7 +3186,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False, "is_cof": False,
"on_acc__trigramme": "000", "on_acc__trigramme": "000",
"valid_by__trigramme": None, "valid_by__trigramme": None,
"opes": [ "entries": [
{ {
"id": operation_list[0].pk, "id": operation_list[0].pk,
"addcost_amount": None, "addcost_amount": None,
@ -3234,7 +3239,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
""" """
url_name = "kfet.kpsul.cancel_operations" url_name = "kfet.operations.cancel"
url_expected = "/k-fet/k-psul/cancel_operations" url_expected = "/k-fet/k-psul/cancel_operations"
http_methods = ["POST"] http_methods = ["POST"]
@ -3353,7 +3358,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
) )
self.assertDictEqual( self.assertDictEqual(
json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} json_data,
{
"canceled": [
{
"id": operation.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
}
],
"errors": {},
"warnings": {},
"opegroups_to_update": [
{
"id": group.pk,
"amount": str(group.amount),
"is_cof": group.is_cof,
}
],
},
) )
self.account.refresh_from_db() self.account.refresh_from_db()
@ -3365,26 +3389,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with( self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul", "kfet.kpsul",
{ {"checkouts": [], "articles": [{"id": self.article.pk, "stock": 22}]},
"opegroups": [
{
"cancellation": True,
"id": group.pk,
"amount": Decimal("0.00"),
"is_cof": False,
}
],
"opes": [
{
"cancellation": True,
"id": operation.pk,
"canceled_by__trigramme": None,
"canceled_at": self.now + timedelta(seconds=15),
}
],
"checkouts": [],
"articles": [{"id": self.article.pk, "stock": 22}],
},
) )
def test_purchase_with_addcost(self): def test_purchase_with_addcost(self):
@ -3541,7 +3546,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
) )
self.assertDictEqual( self.assertDictEqual(
json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} json_data,
{
"canceled": [
{
"id": operation.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
}
],
"errors": {},
"warnings": {},
"opegroups_to_update": [
{
"id": group.pk,
"amount": str(group.amount),
"is_cof": group.is_cof,
}
],
},
) )
self.account.refresh_from_db() self.account.refresh_from_db()
@ -3554,22 +3578,6 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with( self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [
{
"cancellation": True,
"id": group.pk,
"amount": Decimal("0.00"),
"is_cof": False,
}
],
"opes": [
{
"cancellation": True,
"id": operation.pk,
"canceled_by__trigramme": None,
"canceled_at": self.now + timedelta(seconds=15),
}
],
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("89.25")}], "checkouts": [{"id": self.checkout.pk, "balance": Decimal("89.25")}],
"articles": [], "articles": [],
}, },
@ -3625,7 +3633,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
) )
self.assertDictEqual( self.assertDictEqual(
json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} json_data,
{
"canceled": [
{
"id": operation.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
}
],
"errors": {},
"warnings": {},
"opegroups_to_update": [
{
"id": group.pk,
"amount": str(group.amount),
"is_cof": group.is_cof,
}
],
},
) )
self.account.refresh_from_db() self.account.refresh_from_db()
@ -3638,22 +3665,6 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with( self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul", "kfet.kpsul",
{ {
"opegroups": [
{
"cancellation": True,
"id": group.pk,
"amount": Decimal("0.00"),
"is_cof": False,
}
],
"opes": [
{
"cancellation": True,
"id": operation.pk,
"canceled_by__trigramme": None,
"canceled_at": self.now + timedelta(seconds=15),
}
],
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("110.75")}], "checkouts": [{"id": self.checkout.pk, "balance": Decimal("110.75")}],
"articles": [], "articles": [],
}, },
@ -3709,7 +3720,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
) )
self.assertDictEqual( self.assertDictEqual(
json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} json_data,
{
"canceled": [
{
"id": operation.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
}
],
"errors": {},
"warnings": {},
"opegroups_to_update": [
{
"id": group.pk,
"amount": str(group.amount),
"is_cof": group.is_cof,
}
],
},
) )
self.account.refresh_from_db() self.account.refresh_from_db()
@ -3720,27 +3750,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(self.checkout.balance, Decimal("100.00")) self.assertEqual(self.checkout.balance, Decimal("100.00"))
self.kpsul_consumer_mock.group_send.assert_called_with( self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul", "kfet.kpsul", {"checkouts": [], "articles": []},
{
"opegroups": [
{
"cancellation": True,
"id": group.pk,
"amount": Decimal("0.00"),
"is_cof": False,
}
],
"opes": [
{
"cancellation": True,
"id": operation.pk,
"canceled_by__trigramme": None,
"canceled_at": self.now + timedelta(seconds=15),
}
],
"checkouts": [],
"articles": [],
},
) )
@mock.patch("django.utils.timezone.now") @mock.patch("django.utils.timezone.now")
@ -3961,13 +3971,33 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
group.refresh_from_db() group.refresh_from_db()
self.assertEqual(group.amount, Decimal("10.75")) self.assertEqual(group.amount, Decimal("10.75"))
self.assertEqual(group.opes.exclude(canceled_at=None).count(), 3) self.assertEqual(group.opes.exclude(canceled_at=None).count(), 3)
self.maxDiff = None
self.assertDictEqual( self.assertDictEqual(
json_data, json_data,
{ {
"canceled": [operation1.pk, operation2.pk], "canceled": [
"warnings": {"already_canceled": [operation3.pk]}, {
"id": operation1.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
},
{
"id": operation2.id,
# l'encodage des dates en JSON est relou...
"canceled_at": mock.ANY,
"canceled_by__trigramme": None,
},
],
"errors": {}, "errors": {},
"warnings": {"already_canceled": [operation3.pk]},
"opegroups_to_update": [
{
"id": group.pk,
"amount": str(group.amount),
"is_cof": group.is_cof,
}
],
}, },
) )
@ -4121,12 +4151,18 @@ class HistoryJSONViewTests(ViewTestCaseMixin, TestCase):
url_expected = "/k-fet/history.json" url_expected = "/k-fet/history.json"
auth_user = "user" auth_user = "user"
auth_forbidden = [None] auth_forbidden = [None, "noaccount"]
def test_ok(self): def test_ok(self):
r = self.client.post(self.url) r = self.client.post(self.url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
def get_users_extra(self):
noaccount = User.objects.create(username="noaccount")
noaccount.set_password("noaccount")
noaccount.save()
return {"noaccount": noaccount}
class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase): class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
url_name = "kfet.account.read.json" url_name = "kfet.account.read.json"

View file

@ -219,8 +219,8 @@ urlpatterns = [
), ),
path( path(
"k-psul/cancel_operations", "k-psul/cancel_operations",
views.kpsul_cancel_operations, views.cancel_operations,
name="kfet.kpsul.cancel_operations", name="kfet.operations.cancel",
), ),
path( path(
"k-psul/articles_data", "k-psul/articles_data",
@ -252,7 +252,7 @@ urlpatterns = [
# ----- # -----
# Transfers urls # Transfers urls
# ----- # -----
path("transfers/", views.transfers, name="kfet.transfers"), path("transfers/", views.TransferView.as_view(), name="kfet.transfers"),
path("transfers/new", views.transfers_create, name="kfet.transfers.create"), path("transfers/new", views.transfers_create, name="kfet.transfers.create"),
path("transfers/perform", views.perform_transfers, name="kfet.transfers.perform"), path("transfers/perform", views.perform_transfers, name="kfet.transfers.perform"),
path("transfers/cancel", views.cancel_transfers, name="kfet.transfers.cancel"), path("transfers/cancel", views.cancel_transfers, name="kfet.transfers.cancel"),

View file

@ -12,7 +12,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Permission, User from django.contrib.auth.models import Permission, User
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db import transaction from django.db import transaction
from django.db.models import Count, F, Prefetch, Sum from django.db.models import Count, F, Prefetch, Q, Sum
from django.forms import formset_factory from django.forms import formset_factory
from django.http import Http404, JsonResponse from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
@ -1156,9 +1156,10 @@ def kpsul_perform_operations(request):
# Websocket data # Websocket data
websocket_data = {} websocket_data = {}
websocket_data["opegroups"] = [ websocket_data["groups"] = [
{ {
"add": True, "add": True,
"type": "operation",
"id": operationgroup.pk, "id": operationgroup.pk,
"amount": operationgroup.amount, "amount": operationgroup.amount,
"checkout__name": operationgroup.checkout.name, "checkout__name": operationgroup.checkout.name,
@ -1169,7 +1170,7 @@ def kpsul_perform_operations(request):
operationgroup.valid_by and operationgroup.valid_by.trigramme or None operationgroup.valid_by and operationgroup.valid_by.trigramme or None
), ),
"on_acc__trigramme": operationgroup.on_acc.trigramme, "on_acc__trigramme": operationgroup.on_acc.trigramme,
"opes": [], "entries": [],
} }
] ]
for operation in operations: for operation in operations:
@ -1187,7 +1188,7 @@ def kpsul_perform_operations(request):
"canceled_by__trigramme": None, "canceled_by__trigramme": None,
"canceled_at": None, "canceled_at": None,
} }
websocket_data["opegroups"][0]["opes"].append(ope_data) websocket_data["groups"][0]["entries"].append(ope_data)
# Need refresh from db cause we used update on queryset # Need refresh from db cause we used update on queryset
operationgroup.checkout.refresh_from_db() operationgroup.checkout.refresh_from_db()
websocket_data["checkouts"] = [ websocket_data["checkouts"] = [
@ -1207,7 +1208,7 @@ def kpsul_perform_operations(request):
@teamkfet_required @teamkfet_required
@kfet_password_auth @kfet_password_auth
def kpsul_cancel_operations(request): def cancel_operations(request):
# Pour la réponse # Pour la réponse
data = {"canceled": [], "warnings": {}, "errors": {}} data = {"canceled": [], "warnings": {}, "errors": {}}
@ -1363,7 +1364,11 @@ def kpsul_cancel_operations(request):
.filter(pk__in=opegroups_pk) .filter(pk__in=opegroups_pk)
.order_by("pk") .order_by("pk")
) )
opes = sorted(opes) opes = (
Operation.objects.values("id", "canceled_at", "canceled_by__trigramme")
.filter(pk__in=opes)
.order_by("pk")
)
checkouts_pk = [checkout.pk for checkout in to_checkouts_balances] checkouts_pk = [checkout.pk for checkout in to_checkouts_balances]
checkouts = ( checkouts = (
Checkout.objects.values("id", "balance") Checkout.objects.values("id", "balance")
@ -1374,27 +1379,7 @@ def kpsul_cancel_operations(request):
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk) articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
# Websocket data # Websocket data
websocket_data = {"opegroups": [], "opes": [], "checkouts": [], "articles": []} websocket_data = {"checkouts": [], "articles": []}
for opegroup in opegroups:
websocket_data["opegroups"].append(
{
"cancellation": True,
"id": opegroup["id"],
"amount": opegroup["amount"],
"is_cof": opegroup["is_cof"],
}
)
canceled_by__trigramme = canceled_by and canceled_by.trigramme or None
for ope in opes:
websocket_data["opes"].append(
{
"cancellation": True,
"id": ope,
"canceled_by__trigramme": canceled_by__trigramme,
"canceled_at": canceled_at,
}
)
for checkout in checkouts: for checkout in checkouts:
websocket_data["checkouts"].append( websocket_data["checkouts"].append(
{"id": checkout["id"], "balance": checkout["balance"]} {"id": checkout["id"], "balance": checkout["balance"]}
@ -1405,7 +1390,8 @@ def kpsul_cancel_operations(request):
) )
consumers.KPsul.group_send("kfet.kpsul", websocket_data) consumers.KPsul.group_send("kfet.kpsul", websocket_data)
data["canceled"] = opes data["canceled"] = list(opes)
data["opegroups_to_update"] = list(opegroups)
if opes_already_canceled: if opes_already_canceled:
data["warnings"]["already_canceled"] = opes_already_canceled data["warnings"]["already_canceled"] = opes_already_canceled
return JsonResponse(data) return JsonResponse(data)
@ -1416,49 +1402,86 @@ def history_json(request):
# Récupération des paramètres # Récupération des paramètres
from_date = request.POST.get("from", None) from_date = request.POST.get("from", None)
to_date = request.POST.get("to", None) to_date = request.POST.get("to", None)
limit = request.POST.get("limit", None)
checkouts = request.POST.getlist("checkouts[]", None) checkouts = request.POST.getlist("checkouts[]", None)
accounts = request.POST.getlist("accounts[]", None) accounts = request.POST.getlist("accounts[]", None)
transfers_only = request.POST.get("transfersonly", False)
opes_only = request.POST.get("opesonly", False)
# Construction de la requête (sur les transferts) pour le prefetch
transfer_queryset_prefetch = Transfer.objects.select_related(
"from_acc", "to_acc", "canceled_by"
)
# Le check sur les comptes est dans le prefetch pour les transferts
if accounts:
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc__in=accounts) | Q(to_acc__in=accounts)
)
if not request.user.has_perm("kfet.is_team"):
try:
acc = request.user.profile.account_kfet
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc=acc) | Q(to_acc=acc)
)
except Account.DoesNotExist:
return JsonResponse({}, status=403)
transfer_prefetch = Prefetch(
"transfers", queryset=transfer_queryset_prefetch, to_attr="filtered_transfers"
)
# Construction de la requête (sur les opérations) pour le prefetch # Construction de la requête (sur les opérations) pour le prefetch
queryset_prefetch = Operation.objects.select_related( ope_queryset_prefetch = Operation.objects.select_related(
"article", "canceled_by", "addcost_for" "article", "canceled_by", "addcost_for"
) )
ope_prefetch = Prefetch("opes", queryset=ope_queryset_prefetch)
# Construction de la requête principale # Construction de la requête principale
opegroups = ( opegroups = (
OperationGroup.objects.prefetch_related( OperationGroup.objects.prefetch_related(ope_prefetch)
Prefetch("opes", queryset=queryset_prefetch)
)
.select_related("on_acc", "valid_by") .select_related("on_acc", "valid_by")
.order_by("at") .order_by("at")
) )
transfergroups = (
TransferGroup.objects.prefetch_related(transfer_prefetch)
.select_related("valid_by")
.order_by("at")
)
# Application des filtres # Application des filtres
if from_date: if from_date:
opegroups = opegroups.filter(at__gte=from_date) opegroups = opegroups.filter(at__gte=from_date)
transfergroups = transfergroups.filter(at__gte=from_date)
if to_date: if to_date:
opegroups = opegroups.filter(at__lt=to_date) opegroups = opegroups.filter(at__lt=to_date)
transfergroups = transfergroups.filter(at__lt=to_date)
if checkouts: if checkouts:
opegroups = opegroups.filter(checkout_id__in=checkouts) opegroups = opegroups.filter(checkout__in=checkouts)
transfergroups = TransferGroup.objects.none()
if transfers_only:
opegroups = OperationGroup.objects.none()
if opes_only:
transfergroups = TransferGroup.objects.none()
if accounts: if accounts:
opegroups = opegroups.filter(on_acc_id__in=accounts) opegroups = opegroups.filter(on_acc__in=accounts)
# Un non-membre de l'équipe n'a que accès à son historique # Un non-membre de l'équipe n'a que accès à son historique
if not request.user.has_perm("kfet.is_team"): if not request.user.has_perm("kfet.is_team"):
opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet) opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet)
if limit:
opegroups = opegroups[:limit]
# Construction de la réponse # Construction de la réponse
opegroups_list = [] history_groups = []
for opegroup in opegroups: for opegroup in opegroups:
opegroup_dict = { opegroup_dict = {
"type": "operation",
"id": opegroup.id, "id": opegroup.id,
"amount": opegroup.amount, "amount": opegroup.amount,
"at": opegroup.at, "at": opegroup.at,
"checkout_id": opegroup.checkout_id, "checkout_id": opegroup.checkout_id,
"is_cof": opegroup.is_cof, "is_cof": opegroup.is_cof,
"comment": opegroup.comment, "comment": opegroup.comment,
"opes": [], "entries": [],
"on_acc__trigramme": opegroup.on_acc and opegroup.on_acc.trigramme or None, "on_acc__trigramme": opegroup.on_acc and opegroup.on_acc.trigramme or None,
} }
if request.user.has_perm("kfet.is_team"): if request.user.has_perm("kfet.is_team"):
@ -1482,9 +1505,40 @@ def history_json(request):
ope_dict["canceled_by__trigramme"] = ( ope_dict["canceled_by__trigramme"] = (
ope.canceled_by and ope.canceled_by.trigramme or None ope.canceled_by and ope.canceled_by.trigramme or None
) )
opegroup_dict["opes"].append(ope_dict) opegroup_dict["entries"].append(ope_dict)
opegroups_list.append(opegroup_dict) history_groups.append(opegroup_dict)
return JsonResponse({"opegroups": opegroups_list}) for transfergroup in transfergroups:
if transfergroup.filtered_transfers:
transfergroup_dict = {
"type": "transfer",
"id": transfergroup.id,
"at": transfergroup.at,
"comment": transfergroup.comment,
"entries": [],
}
if request.user.has_perm("kfet.is_team"):
transfergroup_dict["valid_by__trigramme"] = (
transfergroup.valid_by and transfergroup.valid_by.trigramme or None
)
for transfer in transfergroup.filtered_transfers:
transfer_dict = {
"id": transfer.id,
"amount": transfer.amount,
"canceled_at": transfer.canceled_at,
"from_acc": transfer.from_acc.trigramme,
"to_acc": transfer.to_acc.trigramme,
}
if request.user.has_perm("kfet.is_team"):
transfer_dict["canceled_by__trigramme"] = (
transfer.canceled_by and transfer.canceled_by.trigramme or None
)
transfergroup_dict["entries"].append(transfer_dict)
history_groups.append(transfergroup_dict)
history_groups.sort(key=lambda group: group["at"])
return JsonResponse({"groups": history_groups})
@teamkfet_required @teamkfet_required
@ -1544,18 +1598,9 @@ config_update = permission_required("kfet.change_config")(SettingsUpdate.as_view
# ----- # -----
@teamkfet_required @method_decorator(teamkfet_required, name="dispatch")
def transfers(request): class TransferView(TemplateView):
transfers_pre = Prefetch( template_name = "kfet/transfers.html"
"transfers", queryset=(Transfer.objects.select_related("from_acc", "to_acc"))
)
transfergroups = (
TransferGroup.objects.select_related("valid_by")
.prefetch_related(transfers_pre)
.order_by("-at")
)
return render(request, "kfet/transfers.html", {"transfergroups": transfergroups})
@teamkfet_required @teamkfet_required
@ -1746,7 +1791,12 @@ def cancel_transfers(request):
elif hasattr(account, "negative") and not account.negative.balance_offset: elif hasattr(account, "negative") and not account.negative.balance_offset:
account.negative.delete() account.negative.delete()
data["canceled"] = transfers transfers = (
Transfer.objects.values("id", "canceled_at", "canceled_by__trigramme")
.filter(pk__in=transfers)
.order_by("pk")
)
data["canceled"] = list(transfers)
if transfers_already_canceled: if transfers_already_canceled:
data["warnings"]["already_canceled"] = transfers_already_canceled data["warnings"]["already_canceled"] = transfers_already_canceled
return JsonResponse(data) return JsonResponse(data)