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

View file

@ -2,31 +2,59 @@ function dateUTCToParis(date) {
return moment.tz(date, 'UTC').tz('Europe/Paris');
}
// TODO : classifier (later)
function KHistory(options = {}) {
$.extend(this, KHistory.default_options, options);
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.$container.html('');
};
this.addOpeGroup = function (opegroup) {
var $day = this._getOrCreateDay(opegroup['at']);
var $opegroup = this._opeGroupHtml(opegroup);
this.add_history_group = function (group) {
var $day = this._get_or_create_day(group['at']);
var $group = this._group_html(group);
$day.after($opegroup);
$day.after($group);
var trigramme = opegroup['on_acc_trigramme'];
var is_cof = opegroup['is_cof'];
for (var i = 0; i < opegroup['opes'].length; i++) {
var $ope = this._opeHtml(opegroup['opes'][i], is_cof, trigramme);
$ope.data('opegroup', opegroup['id']);
$opegroup.after($ope);
var trigramme = group['on_acc_trigramme'];
var is_cof = group['is_cof'];
var type = group['type']
// TODO : simplifier ça ?
switch (type) {
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 parsed_amount = parseFloat(ope['amount']);
var amount = amountDisplay(parsed_amount, is_cof, trigramme);
@ -54,7 +82,8 @@ function KHistory(options = {}) {
}
$ope_html
.data('ope', ope['id'])
.data('type', 'operation')
.data('id', ope['id'])
.find('.amount').text(amount).end()
.find('.infos1').text(infos1).end()
.find('.infos2').text(infos2).end();
@ -62,54 +91,89 @@ function KHistory(options = {}) {
var addcost_for = ope['addcost_for__trigramme'];
if (addcost_for) {
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'])
this.cancelOpe(ope, $ope_html);
this.cancel_entry(ope, $ope_html);
return $ope_html;
}
this.cancelOpe = function (ope, $ope = null) {
if (!$ope)
$ope = this.findOpe(ope['id']);
this._transfer_html = function (transfer) {
var $transfer_html = $(this.template_transfer);
var parsed_amount = parseFloat(transfer['amount']);
var amount = parsed_amount.toFixed(2) + '€';
var cancel = 'Annulé';
var canceled_at = dateUTCToParis(ope['canceled_at']);
if (ope['canceled_by__trigramme'])
cancel += ' par ' + ope['canceled_by__trigramme'];
cancel += ' le ' + canceled_at.format('DD/MM/YY à HH:mm:ss');
$transfer_html
.data('type', 'transfer')
.data('id', transfer['id'])
.find('.amount').text(amount).end()
.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');
var trigramme = opegroup['on_acc__trigramme'];
var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
var comment = opegroup['comment'] || '';
this.cancel_entry = function (entry, $entry = null) {
if (!$entry)
$entry = this.find_entry(entry["id"], entry["type"]);
$opegroup_html
.data('opegroup', opegroup['id'])
var cancel = 'Annulé';
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('.amount').text(amount).end()
.find('.comment').text(comment).end()
.find('.trigramme').text(trigramme).end();
if (!this.display_trigramme)
$opegroup_html.find('.trigramme').remove();
$group_html.find('.trigramme').remove();
$group_html.find('.info').remove();
if (opegroup['valid_by__trigramme'])
$opegroup_html.find('.valid_by').text('Par ' + opegroup['valid_by__trigramme']);
if (group['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_ser = at.format('YYYY-MM-DD');
var $day = this.$container.find('.day').filter(function () {
@ -118,35 +182,123 @@ function KHistory(options = {}) {
if ($day.length == 1)
return $day;
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) {
return this.$container.find('.opegroup').filter(function () {
return $(this).data('opegroup') == id
this.find_group = function (id, type = "operation") {
return this.$container.find('.group').filter(function () {
return ($(this).data('id') == id && $(this).data("type") == type)
});
}
this.findOpe = function (id) {
return this.$container.find('.ope').filter(function () {
return $(this).data('ope') == id
this.find_entry = function (id, type = 'operation') {
return this.$container.find('.entry').filter(function () {
return ($(this).data('id') == id && $(this).data('type') == type)
});
}
this.cancelOpeGroup = function (opegroup) {
var $opegroup = this.findOpeGroup(opegroup['id']);
var trigramme = $opegroup.find('.trigramme').text();
this.update_opegroup = function (group, type = "operation") {
var $group = this.find_group(group['id'], type);
var trigramme = $group.find('.trigramme').text();
var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
$opegroup.find('.amount').text(amount);
parseFloat(group['amount']), group['is_cof'], trigramme);
$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 = {
container: '#history',
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_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_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_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,
}

View file

@ -5,6 +5,7 @@
{% block extra_head %}
<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/fr.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>
</div>
{% endif %}
<div id="history" class="full"></div>
<div id="history"></div>
</section>
</div><!-- history tab -->
@ -93,29 +94,22 @@ $(document).ready(function() {
khistory = new KHistory({
display_trigramme: false,
});
function getHistory() {
var data = {
fetch_options: {
'accounts': [{{ account.pk }}],
}
});
$.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]);
}
$(document).on('keydown', function (e) {
if (e.keyCode == 46) {
// DEL (Suppr)
khistory.cancel_selected()
}
});
khistory.fetch().done(function () {
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes);
});
}
getHistory();
});
});
</script>

View file

@ -5,6 +5,7 @@
<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>
{{ 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/vendor/moment/moment-timezone-with-data-2012-2022.min.js' %}"></script>
{% endblock %}
@ -27,6 +28,9 @@
<li><b>Comptes</b> {{ filter_form.accounts }}</li>
</ul>
</div>
<div class="buttons">
<button class="btn btn-primary" id="btn-fetch">Valider</button>
</div>
</aside>
{% endblock %}
@ -40,6 +44,8 @@
$(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
window.lock = 0;
khistory = new KHistory();
var $from_date = $('#id_from_date');
@ -67,17 +73,8 @@ $(document).ready(function() {
var accounts = getSelectedMultiple($accounts);
data['accounts'] = accounts;
$.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 nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
khistory.fetch(data).done(function () {
var nb_opes = khistory.$container.find('.entry:not(.canceled)').length;
$('#nb_opes').text(nb_opes);
});
}
@ -112,130 +109,17 @@ $(document).ready(function() {
countSelected: "# sur %"
});
$("input").on('dp.change change', function() {
$("#btn-fetch").on('click', function() {
khistory.reset();
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) {
if (e.keyCode == 46) {
// DEL (Suppr)
var opes_to_cancel = [];
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);
khistory.cancel_selected()
}
});
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();
});
</script>

View file

@ -189,7 +189,7 @@ $(document).ready(function() {
// -----
// Lock to avoid multiple requests
lock = 0;
window.lock = 0;
// Retrieve settings
@ -479,9 +479,9 @@ $(document).ready(function() {
var operations = $('#operation_formset');
function performOperations(password = '') {
if (lock == 1)
if (window.lock == 1)
return false;
lock = 1;
window.lock = 1;
var data = operationGroup.serialize() + '&' + operations.serialize();
$.ajax({
dataType: "json",
@ -497,7 +497,7 @@ $(document).ready(function() {
.done(function(data) {
updatePreviousOp();
coolReset();
lock = 0;
window.lock = 0;
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
@ -513,7 +513,7 @@ $(document).ready(function() {
}
break;
}
lock = 0;
window.lock = 0;
});
}
@ -522,55 +522,6 @@ $(document).ready(function() {
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
// -----
@ -1189,24 +1140,12 @@ $(document).ready(function() {
// History
// -----
khistory = new KHistory();
function getHistory() {
var data = {
khistory = new KHistory({
fetch_options: {
from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
};
$.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]);
}
});
}
opesonly: true,
},
});
var previousop_container = $('#previous_op');
@ -1302,29 +1241,10 @@ $(document).ready(function() {
// 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) {
if (e.keyCode == 46) {
// DEL (Suppr)
var opes_to_cancel = [];
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);
khistory.cancel_selected()
}
});
@ -1333,16 +1253,9 @@ $(document).ready(function() {
// -----
OperationWebSocket.add_handler(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
if (data['opegroups'][i]['add']) {
khistory.addOpeGroup(data['opegroups'][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['groups'].length; i++) {
if (data['groups'][i]['add']) {
khistory.add_history_group(data['groups'][i]);
}
}
for (var i=0; i<data['checkouts'].length; i++) {
@ -1396,7 +1309,7 @@ $(document).ready(function() {
khistory.reset();
resetSettings().done(function (){
getArticles();
getHistory();
khistory.fetch();
displayAddcost();
});
}

View file

@ -1,9 +1,16 @@
{% extends 'kfet/base_col_2.html' %}
{% load staticfiles %}
{% load l10n staticfiles widget_tweaks %}
{% block 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 %}
<div class="buttons">
@ -16,109 +23,31 @@
{% block main %}
<div id="history">
{% for transfergroup in transfergroups %}
<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>
<table id="history" class="table">
</table>
<script type="text/javascript">
$(document).ready(function() {
lock = 0;
function displayErrors(html) {
$.alert({
title: 'Erreurs',
content: html,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
});
}
window.lock = 0;
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
function cancelTransfers(transfers_array, password = '') {
if (lock == 1)
return false
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');
}
});
},
var khistory = new KHistory({
fetch_options:{
transfersonly: true,
}
});
$(document).on('keydown', function (e) {
if (e.keyCode == 46) {
// DEL (Suppr)
var transfers_to_cancel = [];
$('#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.cancel_selected()
}
});
khistory.fetch()
});
</script>

View file

@ -3,7 +3,7 @@ from datetime import datetime, timedelta
from decimal import Decimal
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.urls import reverse
from django.utils import timezone
@ -1997,9 +1997,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"opegroups": [
"groups": [
{
"add": True,
"type": "operation",
"at": mock.ANY,
"amount": Decimal("-5.00"),
"checkout__name": "Checkout",
@ -2008,7 +2009,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False,
"on_acc__trigramme": "000",
"valid_by__trigramme": None,
"opes": [
"entries": [
{
"id": operation.pk,
"addcost_amount": None,
@ -2269,9 +2270,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"opegroups": [
"groups": [
{
"add": True,
"type": "operation",
"at": mock.ANY,
"amount": Decimal("10.75"),
"checkout__name": "Checkout",
@ -2280,7 +2282,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False,
"on_acc__trigramme": "000",
"valid_by__trigramme": "100",
"opes": [
"entries": [
{
"id": operation.pk,
"addcost_amount": None,
@ -2443,9 +2445,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"opegroups": [
"groups": [
{
"add": True,
"type": "operation",
"at": mock.ANY,
"amount": Decimal("-10.75"),
"checkout__name": "Checkout",
@ -2454,7 +2457,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False,
"on_acc__trigramme": "000",
"valid_by__trigramme": None,
"opes": [
"entries": [
{
"id": operation.pk,
"addcost_amount": None,
@ -2601,9 +2604,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"opegroups": [
"groups": [
{
"add": True,
"type": "operation",
"at": mock.ANY,
"amount": Decimal("10.75"),
"checkout__name": "Checkout",
@ -2612,7 +2616,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False,
"on_acc__trigramme": "000",
"valid_by__trigramme": "100",
"opes": [
"entries": [
{
"id": operation.pk,
"addcost_amount": None,
@ -2712,9 +2716,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][
0
]["opes"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2752,9 +2756,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][
0
]["opes"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("0.80"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2790,9 +2794,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("106.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][
0
]["opes"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2826,9 +2830,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("15.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][
0
]["opes"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], 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.assertEqual(self.accounts["addcost"].balance, Decimal("0.00"))
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["opegroups"][
0
]["opes"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], 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(
"kfet.kpsul",
{
"opegroups": [
"groups": [
{
"add": True,
"type": "operation",
"at": mock.ANY,
"amount": Decimal("-9.00"),
"checkout__name": "Checkout",
@ -3181,7 +3186,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
"is_cof": False,
"on_acc__trigramme": "000",
"valid_by__trigramme": None,
"opes": [
"entries": [
{
"id": operation_list[0].pk,
"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"
http_methods = ["POST"]
@ -3353,7 +3358,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
)
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()
@ -3365,26 +3389,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with(
"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": [],
"articles": [{"id": self.article.pk, "stock": 22}],
},
{"checkouts": [], "articles": [{"id": self.article.pk, "stock": 22}]},
)
def test_purchase_with_addcost(self):
@ -3541,7 +3546,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
)
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()
@ -3554,22 +3578,6 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with(
"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")}],
"articles": [],
},
@ -3625,7 +3633,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
)
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()
@ -3638,22 +3665,6 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.kpsul_consumer_mock.group_send.assert_called_with(
"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")}],
"articles": [],
},
@ -3709,7 +3720,26 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
)
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()
@ -3720,27 +3750,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(self.checkout.balance, Decimal("100.00"))
self.kpsul_consumer_mock.group_send.assert_called_with(
"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": [],
"articles": [],
},
"kfet.kpsul", {"checkouts": [], "articles": []},
)
@mock.patch("django.utils.timezone.now")
@ -3961,13 +3971,33 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
group.refresh_from_db()
self.assertEqual(group.amount, Decimal("10.75"))
self.assertEqual(group.opes.exclude(canceled_at=None).count(), 3)
self.maxDiff = None
self.assertDictEqual(
json_data,
{
"canceled": [operation1.pk, operation2.pk],
"warnings": {"already_canceled": [operation3.pk]},
"canceled": [
{
"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": {},
"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"
auth_user = "user"
auth_forbidden = [None]
auth_forbidden = [None, "noaccount"]
def test_ok(self):
r = self.client.post(self.url)
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):
url_name = "kfet.account.read.json"

View file

@ -219,8 +219,8 @@ urlpatterns = [
),
path(
"k-psul/cancel_operations",
views.kpsul_cancel_operations,
name="kfet.kpsul.cancel_operations",
views.cancel_operations,
name="kfet.operations.cancel",
),
path(
"k-psul/articles_data",
@ -252,7 +252,7 @@ urlpatterns = [
# -----
# 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/perform", views.perform_transfers, name="kfet.transfers.perform"),
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.messages.views import SuccessMessageMixin
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.http import Http404, JsonResponse
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["opegroups"] = [
websocket_data["groups"] = [
{
"add": True,
"type": "operation",
"id": operationgroup.pk,
"amount": operationgroup.amount,
"checkout__name": operationgroup.checkout.name,
@ -1169,7 +1170,7 @@ def kpsul_perform_operations(request):
operationgroup.valid_by and operationgroup.valid_by.trigramme or None
),
"on_acc__trigramme": operationgroup.on_acc.trigramme,
"opes": [],
"entries": [],
}
]
for operation in operations:
@ -1187,7 +1188,7 @@ def kpsul_perform_operations(request):
"canceled_by__trigramme": 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
operationgroup.checkout.refresh_from_db()
websocket_data["checkouts"] = [
@ -1207,7 +1208,7 @@ def kpsul_perform_operations(request):
@teamkfet_required
@kfet_password_auth
def kpsul_cancel_operations(request):
def cancel_operations(request):
# Pour la réponse
data = {"canceled": [], "warnings": {}, "errors": {}}
@ -1363,7 +1364,11 @@ def kpsul_cancel_operations(request):
.filter(pk__in=opegroups_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 = (
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)
# Websocket data
websocket_data = {"opegroups": [], "opes": [], "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,
}
)
websocket_data = {"checkouts": [], "articles": []}
for checkout in checkouts:
websocket_data["checkouts"].append(
{"id": checkout["id"], "balance": checkout["balance"]}
@ -1405,7 +1390,8 @@ def kpsul_cancel_operations(request):
)
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:
data["warnings"]["already_canceled"] = opes_already_canceled
return JsonResponse(data)
@ -1416,49 +1402,86 @@ def history_json(request):
# Récupération des paramètres
from_date = request.POST.get("from", None)
to_date = request.POST.get("to", None)
limit = request.POST.get("limit", None)
checkouts = request.POST.getlist("checkouts[]", 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
queryset_prefetch = Operation.objects.select_related(
ope_queryset_prefetch = Operation.objects.select_related(
"article", "canceled_by", "addcost_for"
)
ope_prefetch = Prefetch("opes", queryset=ope_queryset_prefetch)
# Construction de la requête principale
opegroups = (
OperationGroup.objects.prefetch_related(
Prefetch("opes", queryset=queryset_prefetch)
)
OperationGroup.objects.prefetch_related(ope_prefetch)
.select_related("on_acc", "valid_by")
.order_by("at")
)
transfergroups = (
TransferGroup.objects.prefetch_related(transfer_prefetch)
.select_related("valid_by")
.order_by("at")
)
# Application des filtres
if from_date:
opegroups = opegroups.filter(at__gte=from_date)
transfergroups = transfergroups.filter(at__gte=from_date)
if to_date:
opegroups = opegroups.filter(at__lt=to_date)
transfergroups = transfergroups.filter(at__lt=to_date)
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:
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
if not request.user.has_perm("kfet.is_team"):
opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet)
if limit:
opegroups = opegroups[:limit]
# Construction de la réponse
opegroups_list = []
history_groups = []
for opegroup in opegroups:
opegroup_dict = {
"type": "operation",
"id": opegroup.id,
"amount": opegroup.amount,
"at": opegroup.at,
"checkout_id": opegroup.checkout_id,
"is_cof": opegroup.is_cof,
"comment": opegroup.comment,
"opes": [],
"entries": [],
"on_acc__trigramme": opegroup.on_acc and opegroup.on_acc.trigramme or None,
}
if request.user.has_perm("kfet.is_team"):
@ -1482,9 +1505,40 @@ def history_json(request):
ope_dict["canceled_by__trigramme"] = (
ope.canceled_by and ope.canceled_by.trigramme or None
)
opegroup_dict["opes"].append(ope_dict)
opegroups_list.append(opegroup_dict)
return JsonResponse({"opegroups": opegroups_list})
opegroup_dict["entries"].append(ope_dict)
history_groups.append(opegroup_dict)
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
@ -1544,18 +1598,9 @@ config_update = permission_required("kfet.change_config")(SettingsUpdate.as_view
# -----
@teamkfet_required
def transfers(request):
transfers_pre = Prefetch(
"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})
@method_decorator(teamkfet_required, name="dispatch")
class TransferView(TemplateView):
template_name = "kfet/transfers.html"
@teamkfet_required
@ -1746,7 +1791,12 @@ def cancel_transfers(request):
elif hasattr(account, "negative") and not account.negative.balance_offset:
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:
data["warnings"]["already_canceled"] = transfers_already_canceled
return JsonResponse(data)