kpsul/kfet/views.py

658 lines
25 KiB
Python
Raw Normal View History

from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied, ValidationError
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
2016-08-02 10:40:46 +02:00
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import User, Permission
from django.http import HttpResponse, JsonResponse, Http404
from django.forms import modelformset_factory
2016-08-08 00:24:46 +02:00
from django.db import IntegrityError, transaction
2016-08-09 11:02:26 +02:00
from django.db.models import F
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
from django.utils import timezone
2016-08-02 10:40:46 +02:00
from gestioncof.models import CofProfile, Clipper
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
from kfet.models import Account, Checkout, Article, Settings, AccountNegative
from kfet.forms import *
from collections import defaultdict
2016-08-02 10:40:46 +02:00
@login_required
def home(request):
return render(request, "kfet/base.html")
def put_cleaned_data_in_dict(dict, form):
for field in form.cleaned_data:
dict[field] = form.cleaned_data[field]
# -----
# Account views
# -----
# Account - General
2016-08-02 10:40:46 +02:00
@login_required
@permission_required('kfet.is_team')
def account(request):
accounts = Account.objects.order_by('trigramme')
return render(request, "kfet/account.html", { 'accounts' : accounts })
@login_required
@permission_required('kfet.is_team')
def account_is_validandfree_ajax(request):
if not request.GET.get("trigramme"):
raise Http404
trigramme = request.GET.get("trigramme")
data = Account.is_validandfree(trigramme)
return JsonResponse(data)
# Account - Create
@login_required
@permission_required('kfet.is_team')
def account_create(request):
2016-08-02 10:40:46 +02:00
# A envoyer au template
data_template = {
'account_trigramme_form': AccountTriForm(),
'errors' : {},
2016-08-02 10:40:46 +02:00
}
# Enregistrement
if request.method == "POST":
# Pour indiquer la tentative d'enregistrement au template
# Checking permission
if not request.user.has_perm('kfet.add_account'):
raise PermissionDenied
2016-08-02 10:40:46 +02:00
# Peuplement des forms
username = request.POST.get('username')
try:
user = User.objects.get(username=username)
2016-08-02 10:40:46 +02:00
(cof, _) = CofProfile.objects.get_or_create(user=user)
user_form = UserForm(request.POST, instance=user)
cof_form = CofForm(request.POST, instance=cof)
2016-08-02 10:40:46 +02:00
except User.DoesNotExist:
user_form = UserForm(request.POST)
cof_form = CofForm(request.POST)
trigramme_form = AccountTriForm(request.POST)
account_form = AccountNoTriForm(request.POST)
2016-08-02 10:40:46 +02:00
# Ajout des erreurs pour le template
data_template['errors']['user_form'] = user_form.errors
data_template['errors']['cof_form'] = cof_form.errors
data_template['errors']['trigramme_form'] = trigramme_form.errors
data_template['errors']['account_form'] = account_form.errors
if all((user_form.is_valid(), cof_form.is_valid(),
trigramme_form.is_valid(), account_form.is_valid())):
data = {}
# Fill data for Account.save()
2016-08-02 10:40:46 +02:00
put_cleaned_data_in_dict(data, user_form)
put_cleaned_data_in_dict(data, cof_form)
try:
account = trigramme_form.save(data = data)
account_form = AccountNoTriForm(request.POST, instance=account)
account_form.save()
messages.success(request, 'Compte créé : %s' % account.trigramme)
2016-08-02 10:40:46 +02:00
except Account.UserHasAccount as e:
messages.error(request, \
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme)
2016-08-02 10:40:46 +02:00
return render(request, "kfet/account_create.html", data_template)
2016-08-02 10:40:46 +02:00
def account_form_set_readonly_fields(user_form, cof_form):
2016-08-02 10:40:46 +02:00
user_form.fields['username'].widget.attrs['readonly'] = True
cof_form.fields['login_clipper'].widget.attrs['readonly'] = True
cof_form.fields['is_cof'].widget.attrs['disabled'] = True
@login_required
@permission_required('kfet.is_team')
def account_create_ajax(request, username=None, login_clipper=None):
2016-08-02 10:40:46 +02:00
user = None
if login_clipper:
# à partir d'un clipper
# le user associé à ce clipper ne devrait pas encore existé
clipper = get_object_or_404(Clipper, username = login_clipper)
try:
# Vérification que clipper ne soit pas déjà dans User
user = User.objects.get(username=login_clipper)
2016-08-02 10:40:46 +02:00
# Ici, on nous a menti, le user existe déjà
username = user.username
login_clipper = None
except User.DoesNotExist:
# Clipper (sans user déjà existant)
# UserForm - Prefill + Création
user_initial_data = {
'username' : login_clipper,
'email' : login_clipper + "@clipper.ens.fr"}
if clipper.fullname:
# Prefill du nom et prénom
names = clipper.fullname.split()
# Le premier, c'est le prénom
user_initial_data['first_name'] = names[0]
if len(names) > 1:
# Si d'autres noms -> tous dans le nom de famille
user_initial_data['last_name'] = " ".join(names[1:])
user_form = UserForm(initial = user_initial_data)
# CofForm - Prefill + Création
cof_initial_data = { 'login_clipper': login_clipper }
cof_form = CofForm(initial = cof_initial_data)
# AccountForm
account_form = AccountForm()
# Protection (read-only) des champs username et login_clipper
account_form_set_readonly_fields(user_form, cof_form)
2016-08-02 10:40:46 +02:00
if username:
# le user existe déjà
user = get_object_or_404(User, username=username)
# récupération du profil cof
(cof, _) = CofProfile.objects.get_or_create(user=user)
# UserForm + CofForm - Création à partir des instances existantes
user_form = UserForm(instance = user)
cof_form = CofForm(instance = cof)
# AccountForm
account_form = AccountNoTriForm()
2016-08-02 10:40:46 +02:00
# Protection (read-only) des champs username et login_clipper
account_form_set_readonly_fields(user_form, cof_form)
2016-08-02 10:40:46 +02:00
elif not login_clipper:
# connaît pas du tout, faut tout remplir
user_form = UserForm()
cof_form = CofForm()
account_form = AccountNoTriForm()
2016-08-02 10:40:46 +02:00
return render(request, "kfet/account_create_form.html", {
2016-08-02 10:40:46 +02:00
'account_form' : account_form,
'cof_form' : cof_form,
'user_form' : user_form,
})
# Account - Read
@login_required
def account_read(request, trigramme):
try:
account = Account.objects.get(trigramme=trigramme)
except Account.DoesNotExist:
raise Http404
# Checking permissions
if not request.user.has_perm('kfet.is_team') \
and request.user != account.user:
raise PermissionDenied
return render(request, "kfet/account_read.html", { 'account' : account })
# Account - Update
@login_required
def account_update(request, trigramme):
try:
account = Account.objects.get(trigramme=trigramme)
except Account.DoesNotExist:
raise Http404
# Checking permissions
if not request.user.has_perm('kfet.is_team') \
and request.user != account.user:
raise PermissionDenied
if request.method == "POST":
# Update attempt
# Checking permissions
if not request.user.has_perm('kfet.change_account') \
and request.user != account.user:
raise PermissionDenied
# Peuplement des forms
if request.user.has_perm('kfet.change_account'):
account_form = AccountForm(request.POST, instance = account)
else:
account_form = AccountRestrictForm(request.POST, instance = account)
cof_form = CofRestrictForm(request.POST, instance=account.cofprofile)
user_form = UserRestrictForm(request.POST, instance=account.user)
if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())):
data = {}
# Fill data for Account.save()
put_cleaned_data_in_dict(data, user_form)
put_cleaned_data_in_dict(data, cof_form)
# Updating
account_form.save(data = data)
if request.user == account.user:
messages.success(request, \
'Vos informations ont été mises à jour')
else:
messages.success(request, \
'Informations du compte %s mises à jour' % account.trigramme)
return redirect('kfet.account.read', account.trigramme)
else:
messages.error(request, \
'Informations non mises à jour. Corrigez les erreurs')
else:
# No update attempt
if request.user.has_perm('kfet.is_team'):
account_form = AccountForm(instance = account)
else:
account_form = AccountRestrictForm(instance = account)
cof_form = CofRestrictForm(instance = account.cofprofile)
user_form = UserRestrictForm(instance = account.user)
return render(request, "kfet/account_update.html", {
'account' : account,
'account_form' : account_form,
'cof_form' : cof_form,
'user_form' : user_form,
})
# -----
# Checkout views
# -----
# Checkout - General
class CheckoutList(ListView):
model = Checkout
template_name = 'kfet/checkout.html'
context_object_name = 'checkouts'
# Checkout - Create
class CheckoutCreate(SuccessMessageMixin, CreateView):
model = Checkout
template_name = 'kfet/checkout_create.html'
form_class = CheckoutForm
success_message = 'Nouvelle caisse : %(name)s'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('add_checkout'):
raise PermissionDenied
# Creating
form.instance.created_by = self.request.user.profile.account_kfet
return super(CheckoutCreate, self).form_valid(form)
# Checkout - Read
class CheckoutRead(DetailView):
model = Checkout
template_name = 'kfet/checkout_read.html'
context_object_name = 'checkout'
# Checkout - Update
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
model = Checkout
template_name = 'kfet/checkout_update.html'
form_class = CheckoutRestrictForm
success_message = 'Informations mises à jour pour la caisse : %(name)s'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('change_checkout'):
raise PermissionDenied
# Updating
return super(CheckoutUpdate, self).form_valid(form)
# -----
# Article views
# -----
# Article - General
class ArticleList(ListView):
model = Article
queryset = Article.objects.order_by('category', '-is_sold', 'name')
template_name = 'kfet/article.html'
context_object_name = 'articles'
# Article - Create
class ArticleCreate(SuccessMessageMixin, CreateView):
model = Article
template_name = 'kfet/article_create.html'
form_class = ArticleForm
success_message = 'Nouvel item : %(category)s - %(name)s'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('add_article'):
raise PermissionDenied
# Creating
return super(ArticleCreate, self).form_valid(form)
# Article - Read
class ArticleRead(DetailView):
model = Article
template_name = 'kfet/article_read.html'
context_object_name = 'article'
# Article - Update
class ArticleUpdate(UpdateView):
model = Article
template_name = 'kfet/article_update.html'
form_class = ArticleRestrictForm
success_message = "Informations mises à jour pour l'article : %(name)s"
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('change_article'):
raise PermissionDenied
# Updating
return super(ArticleUpdate, self).form_valid(form)
# -----
# K-Psul
# -----
@permission_required('kfet.is_team')
def kpsul(request):
data = {}
data['operationgroup_form'] = KPsulOperationGroupForm()
data['trigramme_form'] = KPsulAccountForm()
data['checkout_form'] = KPsulCheckoutForm()
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
data['operation_formset'] = operation_formset
return render(request, 'kfet/kpsul.html', data)
@permission_required('kfet.is_team')
def kpsul_account_data(request):
trigramme = request.POST.get('trigramme', '')
account = get_object_or_404(Account, trigramme=trigramme)
data = { 'pk': account.pk, 'name': account.name, 'email': account.email,
'is_cof': account.is_cof, 'promo': account.promo,
'balance': account.balance, 'is_frozen': account.is_frozen,
'departement': account.departement, 'nickname': account.nickname }
return JsonResponse(data)
@permission_required('kfet.is_team')
def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0)
checkout = get_object_or_404(Checkout, pk=pk)
data = { 'pk': checkout.pk, 'name': checkout.name, 'balance': checkout.balance,
'valid_from': checkout.valid_from, 'valid_to': checkout.valid_to }
return JsonResponse(data)
2016-08-09 11:02:26 +02:00
def get_missing_perms(required_perms, user):
missing_perms_codenames = [ (perm.split('.'))[1]
for perm in required_perms if not user.has_perm(perm)]
missing_perms = list(
Permission.objects
.filter(codename__in=missing_perms_codenames)
.values_list('name', flat=True))
return missing_perms
@permission_required('kfet.is_team')
def kpsul_perform_operations(request):
# Initializing response data
data = defaultdict(list)
# Checking operationgroup
operationgroup_form = KPsulOperationGroupForm(request.POST)
if not operationgroup_form.is_valid():
data['errors'].append({'operation_group': list(operationgroup_form.errors)})
# Checking operation_formset
operation_formset = KPsulOperationFormSet(request.POST)
if not operation_formset.is_valid():
data['errors'].append({'operations': list(operation_formset.errors) })
# Returning BAD REQUEST if errors
if 'errors' in data:
return JsonResponse(data, status=400)
# Pre-saving (no commit)
operationgroup = operationgroup_form.save(commit = False)
operations = operation_formset.save(commit = False)
# Specific account's checking
if operationgroup.on_acc.is_cash:
for operation in operations:
if operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]:
data['errors'].append(
{'account': 'Charge et retrait impossible sur LIQ'})
return JsonResponse(data, status=400)
# Retrieving COF grant
cof_grant = Settings.SUBVENTION_COF()
# Retrieving addcosts data
addcost_amount = Settings.ADDCOST_AMOUNT()
addcost_for = Settings.ADDCOST_FOR()
# Initializing vars
2016-08-09 11:02:26 +02:00
required_perms = set()
cof_grant_divisor = 1 + cof_grant / 100
is_addcost = (addcost_for and addcost_amount
and addcost_for != operationgroup.on_acc)
addcost_total = 0
to_checkout_balance = 0
# 1. Calculating amount of each PURCHASE operations
# 1.1 Standard price for n articles
# 1.2 Adding addcost if there is one
# 1.3 Taking into account cof status
# 2. Updating (no commit) stock of article for PURCHASE operations
# 3. Calculating amount of operation group
# 4. Adding required permissions to perform each operation
# 5. Calculating total addcost
# and adding addcost_for in operation instance
# 6. Calculating diff for checkout's balance
for operation in operations:
if operation.type == Operation.PURCHASE:
# 1.1
operation.amount = - operation.article.price * operation.article_nb
if is_addcost:
# 1.2
operation.addcost_amount = addcost_amount * operation.article_nb
operation.amount -= operation.addcost_amount
# 5
addcost_total += operation.addcost_amount
operation.addcost_for = addcost_for
# 6
if operationgroup.on_acc.is_cash:
to_checkout_balance += -operation.amount
# 1.3
if operationgroup.on_acc.is_cof:
operation.amount = operation.amount / cof_grant_divisor
# 2
operation.article.stock -= operation.article_nb
else:
# Ope.type is deposit or withdraw
# 6 too
to_checkout_balance += operation.amount
# 3
operationgroup.amount += operation.amount
# 4
if operation.type == Operation.DEPOSIT:
2016-08-09 11:02:26 +02:00
required_perms.add('kfet.perform_deposit')
# Starting transaction to ensure data consistency
# Using select_for_update where it is critical
try:
2016-08-08 00:24:46 +02:00
with transaction.atomic():
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
on_acc = operationgroup.on_acc
on_acc = Account.objects.select_for_update().get(pk=on_acc.pk)
# Adding required permissions to perform operation group
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
(opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation(
amount = operationgroup.amount)
2016-08-09 11:02:26 +02:00
required_perms |= opegroup_perms
# Checking authenticated user has all perms
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
if stop_ope or not request.user.has_perms(required_perms):
raise PermissionDenied
# If 1 perm is required, saving who perform the operations
if len(required_perms) > 0:
operationgroup.valid_by = request.user.profile.account_kfet
# Filling cof status for statistics
operationgroup.is_cof = on_acc.is_cof
# If not cash account,
# saving account's balance and adding to Negative if not in
if not on_acc.is_cash:
on_acc.balance += operationgroup.amount
on_acc.save()
if on_acc.balance < 0:
if hasattr(on_acc, 'negative'):
if not on_acc.negative.start:
on_acc.negative.start = timezone.now()
on_acc.negative.save()
else:
negative = AccountNegative(
account = on_acc, start = timezone.now())
negative.save()
elif (hasattr(on_acc, 'negative')
and not on_acc.negative.balance_offset):
on_acc.negative.delete()
# Updating checkout's balance
operationgroup.checkout.balance += to_checkout_balance
operationgroup.checkout.save()
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
# Saving addcost_for with new balance if there is one
if is_addcost:
2016-08-08 04:11:08 +02:00
addcost_for.balance += addcost_total
addcost_for.save()
# Saving operation group
operationgroup.save()
data['operationgroup'] = operationgroup.pk
# Filling operationgroup id for each operations and saving
# Saving articles with new stock
for operation in operations:
operation.group = operationgroup
operation.save()
if operation.type == Operation.PURCHASE:
operation.article.save()
data['operations'].append(operation.pk)
except PermissionDenied:
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
# Sending BAD_REQUEST with missing perms or url to manage negative
missing_perms = \
[ Permission.objects.get(codename=codename).name for codename in (
(perm.split('.'))[1] for perm in
required_perms if not request.user.has_perm(perm)
)]
Gestion des commandes K-Psul donnant un négatif * Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
2016-08-08 07:44:05 +02:00
if missing_perms:
data['errors'].append({'missing_perms': missing_perms })
if stop_ope:
data['errors'].append({'negative': 'url to manage negative'})
return JsonResponse(data, status=403)
return JsonResponse(data)
2016-08-09 11:02:26 +02:00
@permission_required('kfet.is_team')
def kpsul_cancel_operations(request):
# Pour la réponse
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('operation', []))))
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
return JsonResponse(data, status=400)
opes_already_canceled = [] # Déjà annulée
opes = [] # 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
to_groups_amounts = defaultdict(lambda:0) # ------ sur les montants des groupes d'opé
to_checkouts_balances = defaultdict(lambda:0) # ------ sur les balances de caisses
to_articles_stocks = defaultdict(lambda:0) # ------ sur les stocks d'articles
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)
else:
opes.append(ope.pk)
# Si opé il y a plus de CANCEL_DURATION, permission requise
if ope.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
if not ope.group.on_acc.is_cash:
to_accounts_balances[ope.group.on_acc] -= ope.amount
if ope.addcost_for and ope.addcost_amount:
to_accounts_balances[ope.addcost_for] -= ope.addcost_amount
# Pour les groupes d'opés
to_groups_amounts[ope.group] -= ope.amount
# Pour les balances de caisses
if ope.type == Operation.PURCHASE:
if ope.group.on_acc.is_cash:
to_checkouts_balances[ope.group.on_acc] -= - ope.amount
else:
to_checkouts_balances[ope.group.on_acc] -= ope.amount
# Pour les stocks d'articles
if ope.article and ope.article_nb:
to_articles_stocks[ope.article] += ope.article_nb
if not opes:
data['warnings']['already_canceled'] = opes_already_canceled
return JsonResponse(data)
# Checking permissions or stop
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account],
overdraft_duration_max = overdraft_duration_max,
overdraft_amount_max = overdraft_amount_max)
required_perms |= perms
stop_all = stop_all or stop
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'] = True
return JsonResponse(data, status=403)
with transaction.atomic():
canceled_by = required_perms and request.user.profile.account_kfet or None
(Operation.objects.filter(pk__in=opes)
.update(canceled_by=canceled_by, canceled_at=timezone.now()))
for account in to_accounts_balances:
Account.objects.filter(pk=account.pk).update(
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])
for group in to_groups_amounts:
OperationGroup.objects.filter(pk=group.pk).update(
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])
data['canceled'] = opes
if opes_already_canceled:
data['warnings']['already_canceled'] = opes_already_canceled
return JsonResponse(data)