Merge branch 'Aufinal/transferts_historique' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/transferts_historique

This commit is contained in:
Ludovic Stephan 2017-02-04 23:22:54 -02:00
commit 8895daff6a
7 changed files with 313 additions and 251 deletions

View file

@ -40,6 +40,11 @@
width:90px;
}
#history .opegroup .info {
text-align:center;
width:145px;
}
#history .opegroup .valid_by {
padding-left:20px
}
@ -67,6 +72,10 @@
text-align:right;
}
#history .ope .glyphicon {
padding-left:15px;
}
#history .ope .infos2 {
padding-left:15px;
}

View file

@ -15,10 +15,18 @@ function KHistory(options={}) {
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);
if (opegroup['type'] == 'opegroup') {
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);
}
} else {
for (var i=0; i<opegroup['opes'].length; i++) {
var $transfer = this._transferHtml(opegroup['opes'][i]);
$transfer.data('transfergroup', opegroup['id']);
$opegroup.after($transfer);
}
}
}
@ -41,7 +49,8 @@ function KHistory(options={}) {
}
$ope_html
.data('ope', ope['id'])
.data('type', 'ope')
.data('id', ope['id'])
.find('.amount').text(amount).end()
.find('.infos1').text(infos1).end()
.find('.infos2').text(infos2).end();
@ -58,6 +67,25 @@ function KHistory(options={}) {
return $ope_html;
}
this._transferHtml = function(transfer) {
var $transfer_html = $(this.template_transfer);
var parsed_amount = parseFloat(transfer['amount']);
var amount = parsed_amount.toFixed(2) + '€';
$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();
if (transfer['canceled_at'])
this.cancelOpe(transfer, $transfer_html);
return $transfer_html ;
}
this.cancelOpe = function(ope, $ope = null) {
if (!$ope)
$ope = this.findOpe(ope['id']);
@ -72,23 +100,36 @@ function KHistory(options={}) {
}
this._opeGroupHtml = function(opegroup) {
var $opegroup_html = $(this.template_opegroup);
var type = opegroup['type'];
if (type == 'opegroup') {
var $opegroup_html = $(this.template_opegroup);
var trigramme = opegroup['on_acc__trigramme'];
var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
} else {
var $opegroup_html = $(this.template_transfergroup);
var trigramme = '';
var amount = '' ;
}
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'] || '';
$opegroup_html
.data('opegroup', opegroup['id'])
.data('type', type)
.data('id', opegroup['id'])
.find('.time').text(at).end()
.find('.trigramme').text(trigramme).end()
.find('.info').text("Transferts").end()
.find('.amount').text(amount).end()
.find('.comment').text(comment).end()
.find('.trigramme').text(trigramme).end();
.find('.comment').text(comment).end();
if (!this.display_trigramme)
$opegroup_html.find('.trigramme').remove();
$opegroup_html.find('.info').remove();
if (opegroup['valid_by__trigramme'])
$opegroup_html.find('.valid_by').text('Par '+opegroup['valid_by__trigramme']);
@ -114,9 +155,9 @@ function KHistory(options={}) {
});
}
this.findOpe = function(id) {
this.findOpe = function(id, type) {
return this.$container.find('.ope').filter(function() {
return $(this).data('ope') == id
return ($(this).data('id') == id && $(this).data('type') == type)
});
}
@ -134,6 +175,8 @@ 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_transfergroup: '<div class="opegroup"><span class="time"></span><span class="info"></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_transfer: '<div class="ope"><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

@ -138,3 +138,8 @@ function requestAuth(data, callback, focus_next = null) {
});
}
String.prototype.pluralize = function(count, irreg_plural = false) {
plural = irreg_plural ? irreg_plural : this + 's' ;
return (count==1 ? this : plural) ;
}

View file

@ -62,6 +62,8 @@
$(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ settings.subvention_cof|unlocalize }})}
lock = 0 ;
khistory = new KHistory();
var $from_date = $('#from_date');
@ -142,9 +144,10 @@ $(document).ready(function() {
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
var opegroup = $(this).data('opegroup');
var type = $(this).data('type');
var id = $(this).data('id');
$(this).siblings('.ope').filter(function() {
return $(this).data('opegroup') == opegroup
return $(this).data(type) == id
}).addClass('ui-selected');
}
});
@ -156,7 +159,7 @@ $(document).ready(function() {
// DEL (Suppr)
var opes_to_cancel = [];
khistory.$container.find('.ope.ui-selected').each(function () {
opes_to_cancel.push($(this).data('ope'));
opes_to_cancel.push($(this).data('type')+' '+$(this).data('id'));
});
if (opes_to_cancel.length > 0)
confirmCancel(opes_to_cancel);
@ -165,7 +168,10 @@ $(document).ready(function() {
function confirmCancel(opes_to_cancel) {
var nb = opes_to_cancel.length;
var content = nb+" opérations vont être annulées";
var content = nb+' opération'.pluralize(nb)
+' va'.pluralize(nb, ' vont')
+ ' être'
+ ' annulée'.pluralize(nb);
$.confirm({
title: 'Confirmation',
content: content,
@ -179,50 +185,10 @@ $(document).ready(function() {
});
}
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 = '') {
if (lock == 1)
return false
lock = 1 ;
var data = { 'operations' : opes_array }
$.ajax({
dataType: "json",
@ -238,6 +204,7 @@ $(document).ready(function() {
})
.done(function(data) {
khistory.$container.find('.ui-selected').removeClass('ui-selected');
lock = 0 ;
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
@ -251,6 +218,7 @@ $(document).ready(function() {
displayErrors(getErrorsHtml(data));
break;
}
lock = 0 ;
});
}

View file

@ -6,7 +6,11 @@
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
{% endblock %}
{% block title %}Transferts{% endblock %}
@ -31,22 +35,7 @@
<div class="content-right">
<div class="content-right-block">
<h2>Liste des transferts</h2>
<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 %}
<table id="history" class="table">
</table>
</div>
</div>
@ -70,15 +59,80 @@ $(document).ready(function() {
});
}
khistory = new KHistory({
display_trigramme: false,
});
function getHistory() {
var data = {'transfersonly': true};
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' %}",
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]);
}
});
}
khistory.$container.selectable({
filter: 'div.opegroup, div.ope',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
var type = $(this).data('type');
var id = $(this).data('id');
$(this).siblings('.ope').filter(function() {
return $(this).data(type) == id
}).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('type')+' '+$(this).data('id'));
});
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ération'.pluralize(nb)
+' va'.pluralize(nb, ' vont')
+ ' être'
+ ' annulée'.pluralize(nb);
$.confirm({
title: 'Confirmation',
content: content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
confirm: function() {
cancelOperations(opes_to_cancel);
}
});
}
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) {
@ -89,11 +143,7 @@ $(document).ready(function() {
})
.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');
khistory.$container.find('.ui-selected').removeClass('ui-selected');
lock = 0;
})
.fail(function($xhr) {
@ -101,7 +151,7 @@ $(document).ready(function() {
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelTransfers(transfers_array, password);
cancelOperations(opes_array, password);
});
break;
case 400:
@ -112,31 +162,7 @@ $(document).ready(function() {
});
}
$('#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) {
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);
}
});
getHistory();
});

View file

@ -178,8 +178,6 @@ urlpatterns = [
name='kfet.transfers.create'),
url(r'^transfers/perform$', views.perform_transfers,
name='kfet.transfers.perform'),
url(r'^transfers/cancel$', views.cancel_transfers,
name='kfet.transfers.cancel'),
# -----
# Inventories urls

View file

@ -18,7 +18,7 @@ from django.contrib.auth.models import User, Permission, Group
from django.http import HttpResponse, JsonResponse, Http404
from django.forms import modelformset_factory, formset_factory
from django.db import IntegrityError, transaction
from django.db.models import F, Sum, Prefetch, Count, Func
from django.db.models import Q, F, Sum, Prefetch, Count, Func
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.utils.crypto import get_random_string
@ -26,7 +26,7 @@ from gestioncof.models import CofProfile, Clipper
from kfet.decorators import teamkfet_required
from kfet.models import (Account, Checkout, Article, Settings, AccountNegative,
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
InventoryArticle, Order, OrderArticle)
InventoryArticle, Order, OrderArticle, TransferGroup, Transfer)
from kfet.forms import *
from collections import defaultdict
from kfet import consumers
@ -1097,26 +1097,47 @@ def kpsul_perform_operations(request):
@teamkfet_required
def kpsul_cancel_operations(request):
# Pour la réponse
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
data = {'canceled': {}, 'warnings': {}, 'errors': {}}
# Checking if BAD REQUEST (opes_pk not int or not existing)
try:
# Set pour virer les doublons
opes_post = set(map(int, filter(None, request.POST.getlist('operations[]', []))))
opes_post = set(map(lambda s: int(s.split()[1]),
filter(lambda s: s.split()[0] == 'ope',
request.POST.getlist('operations[]', []))))
transfers_post = \
set(map(lambda s: int(s.split()[1]),
filter(lambda s: s.split()[0] == 'transfer',
request.POST.getlist('operations[]', []))))
except ValueError:
return JsonResponse(data, status=400)
opes_all = (
Operation.objects
.select_related('group', 'group__on_acc', 'group__on_acc__negative')
.filter(pk__in=opes_post))
opes_pk = [ ope.pk for ope in opes_all ]
opes_notexisting = [ ope for ope in opes_post if ope not in opes_pk ]
if opes_notexisting:
data['errors']['opes_notexisting'] = opes_notexisting
transfers_all = (
Transfer.objects
.select_related('group', 'from_acc', 'from_acc__negative',
'to_acc', 'to_acc__negative')
.filter(pk__in=transfers_post))
transfers_pk = [transfer.pk for transfer in transfers_all]
transfers_notexisting = [transfer for transfer in transfers_post
if transfer not in transfers_pk]
if transfers_notexisting or opes_notexisting:
if transfers_notexisting:
data['errors']['transfers_notexisting'] = transfers_notexisting
if opes_notexisting:
data['errors']['opes_notexisting'] = opes_notexisting
return JsonResponse(data, status=400)
opes_already_canceled = [] # Déjà annulée
opes = [] # Pas déjà annulée
already_canceled = {} # Opération/Transfert déjà annulé
opes = [] # Pas déjà annulée
transfers = []
required_perms = set()
stop_all = False
cancel_duration = Settings.CANCEL_DURATION()
@ -1127,7 +1148,7 @@ def kpsul_cancel_operations(request):
for ope in opes_all:
if ope.canceled_at:
# Opération déjà annulée, va pour un warning en Response
opes_already_canceled.append(ope.pk)
already_canceled['opes'].append(ope.pk)
else:
opes.append(ope.pk)
# Si opé il y a plus de CANCEL_DURATION, permission requise
@ -1161,7 +1182,7 @@ def kpsul_cancel_operations(request):
if not last_statement or last_statement.at < ope.group.at:
if ope.type == Operation.PURCHASE:
if ope.group.on_acc.is_cash:
to_checkouts_balances[ope.group.checkout] -= - ope.amount
to_checkouts_balances[ope.group.checkout] -= -ope.amount
else:
to_checkouts_balances[ope.group.checkout] -= ope.amount
@ -1174,22 +1195,38 @@ def kpsul_cancel_operations(request):
# est recalculé automatiquement
if ope.article and ope.article_nb:
last_stock = (InventoryArticle.objects
.select_related('inventory')
.filter(article=ope.article)
.order_by('inventory__at')
.last())
.select_related('inventory')
.filter(article=ope.article)
.order_by('inventory__at')
.last())
if not last_stock or last_stock.inventory.at < ope.group.at:
to_articles_stocks[ope.article] += ope.article_nb
if not opes:
data['warnings']['already_canceled'] = opes_already_canceled
for transfer in transfers_all:
if transfer.canceled_at:
# Transfert déjà annulé, va pour un warning en Response
already_canceled['transfers'].append(transfer.pk)
else:
transfers.append(transfer.pk)
# Si transfer il y a plus de CANCEL_DURATION, permission requise
if transfer.group.at + cancel_duration < timezone.now():
required_perms.add('kfet.cancel_old_operations')
# Calcul de toutes modifs à faire en cas de validation
# Pour les balances de comptes
to_accounts_balances[transfer.from_acc] += transfer.amount
to_accounts_balances[transfer.to_acc] += -transfer.amount
if not opes and not transfers:
data['warnings']['already_canceled'] = already_canceled
return JsonResponse(data)
negative_accounts = []
# Checking permissions or stop
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account])
amount=to_accounts_balances[account])
required_perms |= perms
stop_all = stop_all or stop
if stop:
@ -1209,25 +1246,31 @@ def kpsul_cancel_operations(request):
with transaction.atomic():
(Operation.objects.filter(pk__in=opes)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
(Transfer.objects.filter(pk__in=transfers)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances:
Account.objects.filter(pk=account.pk).update(
balance = F('balance') + to_accounts_balances[account])
balance=F('balance') + to_accounts_balances[account])
for checkout in to_checkouts_balances:
Checkout.objects.filter(pk=checkout.pk).update(
balance = F('balance') + to_checkouts_balances[checkout])
balance=F('balance') + to_checkouts_balances[checkout])
for group in to_groups_amounts:
OperationGroup.objects.filter(pk=group.pk).update(
amount = F('amount') + to_groups_amounts[group])
amount=F('amount') + to_groups_amounts[group])
for article in to_articles_stocks:
Article.objects.filter(pk=article.pk).update(
stock = F('stock') + to_articles_stocks[article])
stock=F('stock') + to_articles_stocks[article])
# Websocket data
websocket_data = { 'opegroups': [], 'opes': [], 'checkouts': [], 'articles': [] }
websocket_data = {'opegroups': [], 'opes': [],
'checkouts': [], 'articles': []}
# Need refresh from db cause we used update on querysets
opegroups_pk = [ opegroup.pk for opegroup in to_groups_amounts ]
opegroups_pk = [opegroup.pk for opegroup in to_groups_amounts]
opegroups = (OperationGroup.objects
.values('id','amount','is_cof').filter(pk__in=opegroups_pk))
.values('id', 'amount', 'is_cof')
.filter(pk__in=opegroups_pk))
for opegroup in opegroups:
websocket_data['opegroups'].append({
'cancellation': True,
@ -1244,15 +1287,16 @@ def kpsul_cancel_operations(request):
'canceled_at': canceled_at,
})
# Need refresh from db cause we used update on querysets
checkouts_pk = [ checkout.pk for checkout in to_checkouts_balances]
checkouts_pk = [checkout.pk for checkout in to_checkouts_balances]
checkouts = (Checkout.objects
.values('id', 'balance').filter(pk__in=checkouts_pk))
.values('id', 'balance')
.filter(pk__in=checkouts_pk))
for checkout in checkouts:
websocket_data['checkouts'].append({
'id': checkout['id'],
'balance': checkout['balance']})
# Need refresh from db cause we used update on querysets
articles_pk = [ article.pk for articles in to_articles_stocks]
articles_pk = [article.pk for articles in to_articles_stocks]
articles = Article.objects.values('id', 'stock').filter(pk__in=articles_pk)
for article in articles:
websocket_data['articles'].append({
@ -1260,9 +1304,10 @@ def kpsul_cancel_operations(request):
'stock': article['stock']})
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
data['canceled'] = opes
if opes_already_canceled:
data['warnings']['already_canceled'] = opes_already_canceled
data['canceled']['opes'] = opes
data['canceled']['transfers'] = transfers
if already_canceled:
data['warnings']['already_canceled'] = already_canceled
return JsonResponse(data)
@login_required
@ -1270,45 +1315,77 @@ 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', None)
# 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(
'canceled_by__trigramme', 'addcost_for__trigramme',
'article__name')
ope_prefetch = Prefetch('opes',
queryset = ope_queryset_prefetch)
transfer_queryset_prefetch = Transfer.objects.select_related(
'from_acc__trigramme', 'to_acc__trigramme',
'from_acc__id', 'to_acc__id',
'canceled_by__trigramme')
if accounts:
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc__id__in=accounts) |
Q(to_acc__id__in=accounts))
if not request.user.has_perm('kfet.is_team'):
acc = request.user.profile.account_kfet
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc=acc) | Q(to_acc=acc))
transfer_prefetch = Prefetch('transfers',
queryset=transfer_queryset_prefetch,
to_attr='filtered_transfers')
# Construction de la requête principale
opegroups = (OperationGroup.objects
.prefetch_related(Prefetch('opes', queryset = queryset_prefetch))
.prefetch_related(ope_prefetch)
.select_related('on_acc__trigramme', 'valid_by__trigramme')
.order_by('at')
)
transfergroups = (
TransferGroup.objects
.prefetch_related(transfer_prefetch)
.select_related('valid_by__trigramme')
.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)
transfergroups = TransferGroup.objects.none()
if transfers_only:
opegroups = OperationGroup.objects.none()
if accounts:
opegroups = opegroups.filter(on_acc_id__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 = []
for opegroup in opegroups:
opegroup_dict = {
'type' : 'opegroup',
'id' : opegroup.id,
'amount' : opegroup.amount,
'at' : opegroup.at,
'checkout_id': opegroup.checkout_id,
'is_cof' : opegroup.is_cof,
'comment' : opegroup.comment,
'opes' : [],
@ -1337,7 +1414,41 @@ def history_json(request):
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 })
for transfergroup in transfergroups:
if transfergroup.filtered_transfers:
transfergroup_dict = {
'type': 'transfergroup',
'id': transfergroup.id,
'at': transfergroup.at,
'comment': transfergroup.comment,
'opes': [],
}
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['opes'].append(transfer_dict)
opegroups_list.append(transfergroup_dict)
opegroups_list.sort(key=lambda group: group['at'])
return JsonResponse({'opegroups': opegroups_list})
@teamkfet_required
def kpsul_articles_data(request):
@ -1483,104 +1594,6 @@ def perform_transfers(request):
return JsonResponse(data)
@teamkfet_required
def cancel_transfers(request):
# Pour la réponse
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
# Checking if BAD REQUEST (transfers_pk not int or not existing)
try:
# Set pour virer les doublons
transfers_post = set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
except ValueError:
return JsonResponse(data, status=400)
transfers_all = (
Transfer.objects
.select_related('group', 'from_acc', 'from_acc__negative',
'to_acc', 'to_acc__negative')
.filter(pk__in=transfers_post))
transfers_pk = [ transfer.pk for transfer in transfers_all ]
transfers_notexisting = [ transfer for transfer in transfers_post
if transfer not in transfers_pk ]
if transfers_notexisting:
data['errors']['transfers_notexisting'] = transfers_notexisting
return JsonResponse(data, status=400)
transfers_already_canceled = [] # Déjà annulée
transfers = [] # Pas déjà annulée
required_perms = set()
stop_all = False
cancel_duration = Settings.CANCEL_DURATION()
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
for transfer in transfers_all:
if transfer.canceled_at:
# Transfert déjà annulé, va pour un warning en Response
transfers_already_canceled.append(transfer.pk)
else:
transfers.append(transfer.pk)
# Si transfer il y a plus de CANCEL_DURATION, permission requise
if transfer.group.at + cancel_duration < timezone.now():
required_perms.add('kfet.cancel_old_operations')
# Calcul de toutes modifs à faire en cas de validation
# Pour les balances de comptes
to_accounts_balances[transfer.from_acc] += transfer.amount
to_accounts_balances[transfer.to_acc] += -transfer.amount
if not transfers:
data['warnings']['already_canceled'] = transfers_already_canceled
return JsonResponse(data)
negative_accounts = []
# Checking permissions or stop
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account])
required_perms |= perms
stop_all = stop_all or stop
if stop:
negative_accounts.append(account.trigramme)
print(required_perms)
print(request.user.get_all_permissions())
if stop_all or not request.user.has_perms(required_perms):
missing_perms = get_missing_perms(required_perms, request.user)
if missing_perms:
data['errors']['missing_perms'] = missing_perms
if stop_all:
data['errors']['negative'] = negative_accounts
return JsonResponse(data, status=403)
canceled_by = required_perms and request.user.profile.account_kfet or None
canceled_at = timezone.now()
with transaction.atomic():
(Transfer.objects.filter(pk__in=transfers)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances:
Account.objects.filter(pk=account.pk).update(
balance = F('balance') + to_accounts_balances[account])
account.refresh_from_db()
if account.balance < 0:
if hasattr(account, 'negative'):
if not account.negative.start:
account.negative.start = timezone.now()
account.negative.save()
else:
negative = AccountNegative(
account = account, start = timezone.now())
negative.save()
elif (hasattr(account, 'negative')
and not account.negative.balance_offset):
account.negative.delete()
data['canceled'] = transfers
if transfers_already_canceled:
data['warnings']['already_canceled'] = transfers_already_canceled
return JsonResponse(data)
class InventoryList(ListView):
queryset = (Inventory.objects