kpsul/kfet/views.py
2017-01-27 13:08:50 +01:00

1945 lines
76 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.cache import cache
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.core.urlresolvers import reverse_lazy
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required, permission_required
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.functions import Coalesce
from django.utils import timezone
from django.utils.crypto import get_random_string
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)
from kfet.forms import *
from collections import defaultdict
from kfet import consumers
from datetime import timedelta
import django_cas_ng
import hashlib
import heapq
import statistics
@login_required
def home(request):
return render(request, "kfet/base.html")
@teamkfet_required
def login_genericteam(request):
# Check si besoin de déconnecter l'utilisateur de CAS
profile, _ = CofProfile.objects.get_or_create(user=request.user)
need_cas_logout = False
if profile.login_clipper:
need_cas_logout = True
# Récupèration de la vue de déconnexion de CAS
# Ici, car request sera modifié après
logout_cas = django_cas_ng.views.logout(request)
# Authentification du compte générique
token = GenericTeamToken.objects.create(token=get_random_string(50))
user = authenticate(username="kfet_genericteam", token=token.token)
login(request, user)
if need_cas_logout:
# Vue de déconnexion de CAS
return logout_cas
return render(request, "kfet/login_genericteam.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
@login_required
@teamkfet_required
def account(request):
accounts = Account.objects.select_related('cofprofile__user').order_by('trigramme')
return render(request, "kfet/account.html", { 'accounts' : accounts })
@login_required
@teamkfet_required
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
@teamkfet_required
def account_create_special(request):
# Enregistrement
if request.method == "POST":
trigramme_form = AccountTriForm(request.POST, initial={'balance':0})
balance_form = AccountBalanceForm(request.POST)
# Peuplement des forms
username = request.POST.get('username')
login_clipper = request.POST.get('login_clipper')
forms = get_account_create_forms(
request, username=username, login_clipper=login_clipper)
account_form = forms['account_form']
cof_form = forms['cof_form']
user_form = forms['user_form']
if all((user_form.is_valid(), cof_form.is_valid(),
trigramme_form.is_valid(), account_form.is_valid(),
balance_form.is_valid())):
# Checking permission
if not request.user.has_perm('kfet.special_add_account'):
messages.error(request, 'Permission refusée')
else:
data = {}
# Fill data for Account.save()
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()
balance_form = AccountBalanceForm(request.POST, instance=account)
balance_form.save()
amount = balance_form.cleaned_data['balance']
checkout = Checkout.objects.get(name='Initial')
is_cof = account.is_cof
opegroup = OperationGroup.objects.create(
on_acc=account,
checkout=checkout,
amount = amount,
is_cof = account.is_cof)
ope = Operation.objects.create(
group = opegroup,
type = Operation.INITIAL,
amount = amount,
is_checkout = False)
messages.success(request, 'Compte créé : %s' % account.trigramme)
return redirect('kfet.account.create')
except Account.UserHasAccount as e:
messages.error(request, \
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme)
else:
initial = { 'trigramme': request.GET.get('trigramme', '') }
trigramme_form = AccountTriForm(initial = initial)
balance_form = AccountBalanceForm(initial = {'balance': 0})
account_form = None
cof_form = None
user_form = None
return render(request, "kfet/account_create_special.html", {
'trigramme_form': trigramme_form,
'account_form': account_form,
'cof_form': cof_form,
'user_form': user_form,
'balance_form': balance_form,
})
# Account - Create
@login_required
@teamkfet_required
def account_create(request):
# Enregistrement
if request.method == "POST":
trigramme_form = AccountTriForm(request.POST)
# Peuplement des forms
username = request.POST.get('username')
login_clipper = request.POST.get('login_clipper')
forms = get_account_create_forms(
request, username=username, login_clipper=login_clipper)
account_form = forms['account_form']
cof_form = forms['cof_form']
user_form = forms['user_form']
if all((user_form.is_valid(), cof_form.is_valid(),
trigramme_form.is_valid(), account_form.is_valid())):
# Checking permission
if not request.user.has_perm('kfet.add_account'):
messages.error(request, 'Permission refusée')
else:
data = {}
# Fill data for Account.save()
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)
return redirect('kfet.account.create')
except Account.UserHasAccount as e:
messages.error(request, \
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme)
else:
initial = { 'trigramme': request.GET.get('trigramme', '') }
trigramme_form = AccountTriForm(initial = initial)
account_form = None
cof_form = None
user_form = None
return render(request, "kfet/account_create.html", {
'trigramme_form': trigramme_form,
'account_form': account_form,
'cof_form': cof_form,
'user_form': user_form,
})
def account_form_set_readonly_fields(user_form, cof_form):
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
def get_account_create_forms(request=None, username=None, login_clipper=None):
user = None
clipper = None
if login_clipper and (login_clipper == username or not username):
# à partir d'un clipper
# le user associé à ce clipper ne devrait pas encore exister
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)
# Ici, on nous a menti, le user existe déjà
username = user.username
clipper = None
except User.DoesNotExist:
# Clipper (sans user déjà existant)
# UserForm - Prefill
user_initial = {
'username' : login_clipper,
'email' : "%s@clipper.ens.fr" % login_clipper}
if clipper.fullname:
# Prefill du nom et prénom
names = clipper.fullname.split()
# Le premier, c'est le prénom
user_initial['first_name'] = names[0]
if len(names) > 1:
# Si d'autres noms -> tous dans le nom de famille
user_initial['last_name'] = " ".join(names[1:])
# CofForm - Prefill
cof_initial = { 'login_clipper': login_clipper }
# Form créations
if request:
user_form = UserForm(request.POST, initial=user_initial, from_clipper=True)
cof_form = CofForm(request.POST, initial=cof_initial)
else:
user_form = UserForm(initial=user_initial, from_clipper=True)
cof_form = CofForm(initial=cof_initial)
# Protection (read-only) des champs username et login_clipper
account_form_set_readonly_fields(user_form, cof_form)
if username and not clipper:
try:
user = User.objects.get(username=username)
# le user existe déjà
# récupération du profil cof
(cof, _) = CofProfile.objects.get_or_create(user=user)
# UserForm + CofForm - Création à partir des instances existantes
if request:
user_form = UserForm(request.POST, instance = user)
cof_form = CofForm(request.POST, instance = cof)
else:
user_form = UserForm(instance=user)
cof_form = CofForm(instance=cof)
# Protection (read-only) des champs username, login_clipper et is_cof
account_form_set_readonly_fields(user_form, cof_form)
except User.DoesNotExist:
# le username donnée n'existe pas -> Création depuis rien
# (éventuellement en cours avec erreurs précédemment)
pass
if not user and not clipper:
# connaît pas du tout, faut tout remplir
if request:
user_form = UserForm(request.POST)
cof_form = CofForm(request.POST)
else:
user_form = UserForm()
cof_form = CofForm()
# mais on laisse le username en écriture
cof_form.fields['login_clipper'].widget.attrs['readonly'] = True
cof_form.fields['is_cof'].widget.attrs['disabled'] = True
if request:
account_form = AccountNoTriForm(request.POST)
else:
account_form = AccountNoTriForm()
return {
'account_form': account_form,
'cof_form': cof_form,
'user_form': user_form,
}
@login_required
@teamkfet_required
def account_create_ajax(request, username=None, login_clipper=None):
forms = get_account_create_forms(request=None, username=username, login_clipper=login_clipper)
return render(request, "kfet/account_create_form.html", {
'account_form' : forms['account_form'],
'cof_form' : forms['cof_form'],
'user_form' : forms['user_form'],
})
# Account - Read
@login_required
def account_read(request, trigramme):
try:
account = Account.objects.select_related('negative').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
addcosts = (OperationGroup.objects
.filter(opes__addcost_for=account,opes__canceled_at=None)
.extra({'date':"date(at)"})
.values('date')
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
.order_by('-date'))
return render(request, "kfet/account_read.html", {
'account' : account,
'addcosts': addcosts,
'settings': { 'subvention_cof': Settings.SUBVENTION_COF() },
})
# Account - Update
@login_required
def account_update(request, trigramme):
account = get_object_or_404(Account, trigramme=trigramme)
# Checking permissions
if not request.user.has_perm('kfet.is_team') \
and request.user != account.user:
raise PermissionDenied
if request.user.has_perm('kfet.is_team'):
user_form = UserRestrictTeamForm(instance=account.user)
group_form = UserGroupForm(instance=account.user)
account_form = AccountForm(instance=account)
cof_form = CofRestrictForm(instance=account.cofprofile)
pwd_form = AccountPwdForm()
if account.balance < 0 and not hasattr(account, 'negative'):
AccountNegative.objects.create(account=account, start=timezone.now())
account.refresh_from_db()
if hasattr(account, 'negative'):
negative_form = AccountNegativeForm(instance=account.negative)
else:
negative_form = None
else:
user_form = UserRestrictForm(instance=account.user)
account_form = AccountRestrictForm(instance=account)
cof_form = None
group_form = None
negative_form = None
pwd_form = None
if request.method == "POST":
# Update attempt
success = False
missing_perm = True
if request.user.has_perm('kfet.is_team'):
account_form = AccountForm(request.POST, instance=account)
cof_form = CofRestrictForm(request.POST, instance=account.cofprofile)
user_form = UserRestrictTeamForm(request.POST, instance=account.user)
group_form = UserGroupForm(request.POST, instance=account.user)
pwd_form = AccountPwdForm(request.POST)
if hasattr(account, 'negative'):
negative_form = AccountNegativeForm(request.POST, instance=account.negative)
if (request.user.has_perm('kfet.change_account')
and account_form.is_valid() and cof_form.is_valid()
and user_form.is_valid()):
missing_perm = False
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)
# Checking perm to update password
if (request.user.has_perm('kfet.change_account_password')
and pwd_form.is_valid()):
pwd = pwd_form.cleaned_data['pwd1']
pwd_sha256 = hashlib.sha256(pwd.encode('utf-8')).hexdigest()
Account.objects.filter(pk=account.pk).update(
password = pwd_sha256)
messages.success(request, 'Mot de passe mis à jour')
# Checking perm to manage perms
if (request.user.has_perm('kfet.manage_perms')
and group_form.is_valid()):
group_form.save()
# Checking perm to manage negative
if hasattr(account, 'negative'):
balance_offset_old = 0
if account.negative.balance_offset:
balance_offset_old = account.negative.balance_offset
if (hasattr(account, 'negative')
and request.user.has_perm('kfet.change_accountnegative')
and negative_form.is_valid()):
balance_offset_new = negative_form.cleaned_data['balance_offset']
if not balance_offset_new:
balance_offset_new = 0
balance_offset_diff = balance_offset_new - balance_offset_old
Account.objects.filter(pk=account.pk).update(
balance = F('balance') + balance_offset_diff)
negative_form.save()
if not balance_offset_new and Account.objects.get(pk=account.pk).balance >= 0:
AccountNegative.objects.get(account=account).delete()
success = True
messages.success(request,
'Informations du compte %s mises à jour' % account.trigramme)
if request.user == account.user:
missing_perm = False
account.refresh_from_db()
user_form = UserRestrictForm(request.POST, instance=account.user)
account_form = AccountRestrictForm(request.POST, instance=account)
if user_form.is_valid() and account_form.is_valid():
user_form.save()
account_form.save()
success = True
messages.success(request, 'Vos informations ont été mises à jour')
if missing_perm:
messages.error(request, 'Permission refusée')
if success:
return redirect('kfet.account.read', account.trigramme)
else:
messages.error(request, 'Informations non mises à jour. Corrigez les erreurs')
return render(request, "kfet/account_update.html", {
'account' : account,
'account_form' : account_form,
'cof_form' : cof_form,
'user_form' : user_form,
'group_form' : group_form,
'negative_form': negative_form,
'pwd_form' : pwd_form,
})
@permission_required('kfet.manage_perms')
def account_group(request):
groups = (Group.objects
.filter(name__icontains='K-Fêt')
.prefetch_related('permissions', 'user_set__profile__account_kfet')
)
return render(request, 'kfet/account_group.html', { 'groups': groups })
class AccountGroupCreate(SuccessMessageMixin, CreateView):
model = Group
template_name = 'kfet/account_group_form.html'
form_class = GroupForm
success_message = 'Nouveau groupe : %(name)s'
success_url = reverse_lazy('kfet.account.group')
class AccountGroupUpdate(UpdateView):
queryset = Group.objects.filter(name__icontains='K-Fêt')
template_name = 'kfet/account_group_form.html'
form_class = GroupForm
success_message = 'Groupe modifié : %(name)s'
success_url = reverse_lazy('kfet.account.group')
class AccountNegativeList(ListView):
queryset = (AccountNegative.objects
.select_related('account', 'account__cofprofile__user')
.exclude(account__trigramme='#13'))
template_name = 'kfet/account_negative.html'
context_object_name = 'negatives'
def get_context_data(self, **kwargs):
context = super(AccountNegativeList, self).get_context_data(**kwargs)
context['settings'] = {
'overdraft_amount': Settings.OVERDRAFT_AMOUNT(),
'overdraft_duration': Settings.OVERDRAFT_DURATION(),
}
negs_sum = (AccountNegative.objects
.exclude(account__trigramme='#13')
.aggregate(
bal = Coalesce(Sum('account__balance'),0),
offset = Coalesce(Sum('balance_offset'),0),
)
)
context['negatives_sum'] = negs_sum['bal'] - negs_sum['offset']
return context
# -----
# 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('kfet.add_checkout'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Creating
form.instance.created_by = self.request.user.profile.account_kfet
checkout = form.save()
# Création d'un relevé avec balance initiale
CheckoutStatement.objects.create(
checkout = checkout,
by = self.request.user.profile.account_kfet,
balance_old = checkout.balance,
balance_new = checkout.balance,
amount_taken = 0)
return super(CheckoutCreate, self).form_valid(form)
# Checkout - Read
class CheckoutRead(DetailView):
model = Checkout
template_name = 'kfet/checkout_read.html'
context_object_name = 'checkout'
def get_context_data(self, **kwargs):
context = super(CheckoutRead, self).get_context_data(**kwargs)
context['statements'] = context['checkout'].statements.order_by('-at')
return context
# 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('kfet.change_checkout'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Updating
return super(CheckoutUpdate, self).form_valid(form)
# -----
# Checkout Statement views
# -----
# Checkout Statement - General
class CheckoutStatementList(ListView):
model = CheckoutStatement
queryset = CheckoutStatement.objects.order_by('-at')
template_name = 'kfet/checkoutstatement.html'
context_object_name= 'checkoutstatements'
# Checkout Statement - Create
def getAmountTaken(data):
return Decimal(data.taken_001 * 0.01 + data.taken_002 * 0.02
+ data.taken_005 * 0.05 + data.taken_01 * 0.1
+ data.taken_02 * 0.2 + data.taken_05 * 0.5
+ data.taken_1 * 1 + data.taken_2 * 2
+ data.taken_5 * 5 + data.taken_10 * 10
+ data.taken_20 * 20 + data.taken_50 * 50
+ data.taken_100 * 100 + data.taken_200 * 200
+ data.taken_500 * 500 + float(data.taken_cheque))
def getAmountBalance(data):
return Decimal(data['balance_001'] * 0.01 + data['balance_002'] * 0.02
+ data['balance_005'] * 0.05 + data['balance_01'] * 0.1
+ data['balance_02'] * 0.2 + data['balance_05'] * 0.5
+ data['balance_1'] * 1 + data['balance_2'] * 2
+ data['balance_5'] * 5 + data['balance_10'] * 10
+ data['balance_20'] * 20 + data['balance_50'] * 50
+ data['balance_100'] * 100 + data['balance_200'] * 200
+ data['balance_500'] * 500)
class CheckoutStatementCreate(SuccessMessageMixin, CreateView):
model = CheckoutStatement
template_name = 'kfet/checkoutstatement_create.html'
form_class = CheckoutStatementCreateForm
success_message = 'Nouveau relevé : %(checkout)s - %(at)s'
def get_success_url(self):
return reverse_lazy('kfet.checkout.read', kwargs={'pk':self.kwargs['pk_checkout']})
def get_success_message(self, cleaned_data):
return self.success_message % dict(
cleaned_data,
checkout = self.object.checkout.name,
at = self.object.at)
def get_context_data(self, **kwargs):
context = super(CheckoutStatementCreate, self).get_context_data(**kwargs)
checkout = Checkout.objects.get(pk=self.kwargs['pk_checkout'])
context['checkout'] = checkout
return context
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('kfet.add_checkoutstatement'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Creating
form.instance.amount_taken = getAmountTaken(form.instance)
if not form.instance.not_count:
form.instance.balance_new = getAmountBalance(form.cleaned_data)
form.instance.checkout_id = self.kwargs['pk_checkout']
form.instance.by = self.request.user.profile.account_kfet
return super(CheckoutStatementCreate, self).form_valid(form)
class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView):
model = CheckoutStatement
template_name = 'kfet/checkoutstatement_update.html'
form_class = CheckoutStatementUpdateForm
success_message = 'Relevé modifié'
def get_success_url(self):
return reverse_lazy('kfet.checkout.read', kwargs={'pk':self.kwargs['pk_checkout']})
def get_context_data(self, **kwargs):
context = super(CheckoutStatementUpdate, self).get_context_data(**kwargs)
checkout = Checkout.objects.get(pk=self.kwargs['pk_checkout'])
context['checkout'] = checkout
return context
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('kfet.change_checkoutstatement'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Updating
form.instance.amount_taken = getAmountTaken(form.instance)
return super(CheckoutStatementUpdate, self).form_valid(form)
# -----
# Article views
# -----
# Article - General
class ArticleList(ListView):
queryset = (Article.objects
.select_related('category')
.prefetch_related(Prefetch('inventories',
queryset = Inventory.objects.order_by('-at'),
to_attr = 'inventory'))
.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('kfet.add_article'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Save ici pour save le manytomany suppliers
article = form.save()
# Save des suppliers déjà existant
for supplier in form.cleaned_data['suppliers']:
SupplierArticle.objects.create(
article = article, supplier = supplier)
# Nouveau supplier
supplier_new = form.cleaned_data['supplier_new'].strip()
if supplier_new:
supplier, created = Supplier.objects.get_or_create(
name=supplier_new)
if created:
SupplierArticle.objects.create(
article = article, supplier = supplier)
# Inventaire avec stock initial
inventory = Inventory()
inventory.by = self.request.user.profile.account_kfet
inventory.save()
InventoryArticle.objects.create(
inventory = inventory,
article = article,
stock_old = article.stock,
stock_new = article.stock,
)
# 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'
def get_context_data(self, **kwargs):
context = super(ArticleRead, self).get_context_data(**kwargs)
inventoryarts = (InventoryArticle.objects
.filter(article = self.object)
.select_related('inventory')
.order_by('-inventory__at'))
context['inventoryarts'] = inventoryarts
supplierarts = (SupplierArticle.objects
.filter(article = self.object)
.select_related('supplier')
.order_by('-at'))
context['supplierarts'] = supplierarts
return context
# Article - Update
class ArticleUpdate(SuccessMessageMixin, 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('kfet.change_article'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Save ici pour save le manytomany suppliers
article = form.save()
# Save des suppliers déjà existant
for supplier in form.cleaned_data['suppliers']:
if supplier not in article.suppliers.all():
SupplierArticle.objects.create(
article = article, supplier = supplier)
# On vire les suppliers désélectionnés
for supplier in article.suppliers.all():
if supplier not in form.cleaned_data['suppliers']:
SupplierArticle.objects.filter(
article = article, supplier = supplier).delete()
# Nouveau supplier
supplier_new = form.cleaned_data['supplier_new'].strip()
if supplier_new:
supplier, created = Supplier.objects.get_or_create(
name=supplier_new)
if created:
SupplierArticle.objects.create(
article = article, supplier = supplier)
# Updating
return super(ArticleUpdate, self).form_valid(form)
# -----
# K-Psul
# -----
@teamkfet_required
def kpsul(request):
data = {}
data['operationgroup_form'] = KPsulOperationGroupForm()
data['trigramme_form'] = KPsulAccountForm()
initial = {}
try:
checkout = Checkout.objects.filter(
is_protected=False, valid_from__lte=timezone.now(),
valid_to__gte=timezone.now()).get()
initial['checkout'] = checkout
except (Checkout.DoesNotExist, Checkout.MultipleObjectsReturned):
pass
data['checkout_form'] = KPsulCheckoutForm(initial=initial)
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
data['operation_formset'] = operation_formset
return render(request, 'kfet/kpsul.html', data)
@teamkfet_required
def kpsul_get_settings(request):
addcost_for = Settings.ADDCOST_FOR()
data = {
'subvention_cof': Settings.SUBVENTION_COF(),
'addcost_for' : addcost_for and addcost_for.trigramme or '',
'addcost_amount': Settings.ADDCOST_AMOUNT(),
}
return JsonResponse(data)
@teamkfet_required
def account_read_json(request):
trigramme = request.POST.get('trigramme', '')
account = get_object_or_404(Account, trigramme=trigramme)
data = { 'id': 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,
'trigramme': account.trigramme }
return JsonResponse(data)
@teamkfet_required
def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0)
if not pk:
pk = 0
data = (Checkout.objects
.annotate(
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
last_statement_by_trigramme=F('statements__by__trigramme'),
last_statement_balance=F('statements__balance_new'),
last_statement_at=F('statements__at'))
.values(
'id', 'name', 'balance', 'valid_from', 'valid_to',
'last_statement_balance', 'last_statement_at',
'last_statement_by_trigramme', 'last_statement_by_last_name',
'last_statement_by_first_name')
.select_related(
'statements'
'statements__by',
'statements__by__cofprofile__user')
.filter(pk=pk)
.order_by('statements__at')
.last())
if data is None:
raise Http404
return JsonResponse(data)
@teamkfet_required
def kpsul_update_addcost(request):
addcost_form = AddcostForm(request.POST)
if not addcost_form.is_valid():
data = { 'errors': { 'addcost': list(addcost_form.errors) } }
return JsonResponse(data, status=400)
required_perms = ['kfet.manage_addcosts']
if not request.user.has_perms(required_perms):
data = {
'errors': {
'missing_perms': get_missing_perms(required_perms, request.user)
}
}
return JsonResponse(data, status=403)
trigramme = addcost_form.cleaned_data['trigramme']
account = trigramme and Account.objects.get(trigramme=trigramme) or None
Settings.objects.filter(name='ADDCOST_FOR').update(value_account=account)
Settings.objects.filter(name='ADDCOST_AMOUNT').update(value_decimal=addcost_form.cleaned_data['amount'])
cache.delete('ADDCOST_FOR')
cache.delete('ADDCOST_AMOUNT')
data = {
'addcost': {
'for': trigramme and account.trigramme or None,
'amount': addcost_form.cleaned_data['amount'],
}
}
consumers.KPsul.group_send('kfet.kpsul', data)
return JsonResponse(data)
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
@teamkfet_required
def kpsul_perform_operations(request):
# Initializing response data
data = { 'operationgroup': 0, 'operations': [],
'warnings': {}, 'errors': {} }
# Checking operationgroup
operationgroup_form = KPsulOperationGroupForm(request.POST)
if not operationgroup_form.is_valid():
data['errors']['operation_group'] = list(operationgroup_form.errors)
# Checking operation_formset
operation_formset = KPsulOperationFormSet(request.POST)
if not operation_formset.is_valid():
data['errors']['operations'] = list(operation_formset.errors)
# Returning BAD REQUEST if errors
if data['errors']:
return JsonResponse(data, status=400)
# Pre-saving (no commit)
operationgroup = operationgroup_form.save(commit = False)
operations = operation_formset.save(commit = False)
# Retrieving COF grant
cof_grant = Settings.SUBVENTION_COF()
# Retrieving addcosts data
addcost_amount = Settings.ADDCOST_AMOUNT()
addcost_for = Settings.ADDCOST_FOR()
# Initializing vars
required_perms = set() # Required perms to perform all operations
cof_grant_divisor = 1 + cof_grant / 100
to_addcost_for_balance = 0 # For balance of addcost_for
to_checkout_balance = 0 # For balance of selected checkout
to_articles_stocks = defaultdict(lambda:0) # For stocks articles
is_addcost = (addcost_for and addcost_amount
and addcost_for != operationgroup.on_acc)
need_comment = operationgroup.on_acc.need_comment
# Filling data of each operations + operationgroup + calculating other stuffs
for operation in operations:
if operation.type == Operation.PURCHASE:
operation.amount = - operation.article.price * operation.article_nb
if is_addcost:
operation.addcost_for = addcost_for
operation.addcost_amount = addcost_amount * operation.article_nb
operation.amount -= operation.addcost_amount
to_addcost_for_balance += operation.addcost_amount
if operationgroup.on_acc.is_cash:
operation.is_checkout = True
to_checkout_balance += -operation.amount
else:
operation.is_checkout = False
if operationgroup.on_acc.is_cof:
if is_addcost:
operation.addcost_amount = operation.addcost_amount / cof_grant_divisor
operation.amount = operation.amount / cof_grant_divisor
to_articles_stocks[operation.article] -= operation.article_nb
else:
if operationgroup.on_acc.is_cash:
data['errors']['account'] = 'Charge et retrait impossible sur LIQ'
to_checkout_balance += operation.amount
operationgroup.amount += operation.amount
if operation.type == Operation.DEPOSIT:
required_perms.add('kfet.perform_deposit')
if (not operation.is_checkout
and operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]):
required_perms.add('kfet.edit_balance_account')
need_comment = True
if operationgroup.on_acc.is_cof:
to_addcost_for_balance = to_addcost_for_balance / cof_grant_divisor
(perms, stop) = operationgroup.on_acc.perms_to_perform_operation(
amount = operationgroup.amount)
required_perms |= perms
if need_comment:
operationgroup.comment = operationgroup.comment.strip()
if not operationgroup.comment:
data['errors']['need_comment'] = True
return JsonResponse(data, status=400)
if stop 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:
data['errors']['negative'] = [operationgroup.on_acc.trigramme]
return JsonResponse(data, status=403)
# If 1 perm is required, filling who perform the operations
if required_perms:
operationgroup.valid_by = request.user.profile.account_kfet
# Filling cof status for statistics
operationgroup.is_cof = operationgroup.on_acc.is_cof
# Starting transaction to ensure data consistency
with transaction.atomic():
# If not cash account,
# saving account's balance and adding to Negative if not in
if not operationgroup.on_acc.is_cash:
Account.objects.filter(pk=operationgroup.on_acc.pk).update(
balance = F('balance') + operationgroup.amount)
operationgroup.on_acc.refresh_from_db()
if operationgroup.on_acc.balance < 0:
if hasattr(operationgroup.on_acc, 'negative'):
if not operationgroup.on_acc.negative.start:
operationgroup.on_acc.negative.start = timezone.now()
operationgroup.on_acc.negative.save()
else:
negative = AccountNegative(
account = operationgroup.on_acc, start = timezone.now())
negative.save()
elif (hasattr(operationgroup.on_acc, 'negative')
and not operationgroup.on_acc.negative.balance_offset):
operationgroup.on_acc.negative.delete()
# Updating checkout's balance
if to_checkout_balance:
Checkout.objects.filter(pk=operationgroup.checkout.pk).update(
balance = F('balance') + to_checkout_balance)
# Saving addcost_for with new balance if there is one
if is_addcost and to_addcost_for_balance:
Account.objects.filter(pk=addcost_for.pk).update(
balance = F('balance') + to_addcost_for_balance)
# Saving operation group
operationgroup.save()
data['operationgroup'] = operationgroup.pk
# Filling operationgroup id for each operations and saving
for operation in operations:
operation.group = operationgroup
operation.save()
data['operations'].append(operation.pk)
# Updating articles stock
for article in to_articles_stocks:
Article.objects.filter(pk=article.pk).update(
stock = F('stock') + to_articles_stocks[article])
# Websocket data
websocket_data = {}
websocket_data['opegroups'] = [{
'add': True,
'id': operationgroup.pk,
'amount': operationgroup.amount,
'checkout__name': operationgroup.checkout.name,
'at': operationgroup.at,
'is_cof': operationgroup.is_cof,
'comment': operationgroup.comment,
'valid_by__trigramme': ( operationgroup.valid_by and
operationgroup.valid_by.trigramme or None),
'on_acc__trigramme': operationgroup.on_acc.trigramme,
'opes': [],
}]
for operation in operations:
ope_data = {
'id': operation.pk, 'type': operation.type, 'amount': operation.amount,
'addcost_amount': operation.addcost_amount,
'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None,
'is_checkout': operation.is_checkout,
'article__name': operation.article and operation.article.name or None,
'article_nb': operation.article_nb,
'group_id': operationgroup.pk,
'canceled_by__trigramme': None, 'canceled_at': None,
}
websocket_data['opegroups'][0]['opes'].append(ope_data)
# Need refresh from db cause we used update on queryset
operationgroup.checkout.refresh_from_db()
websocket_data['checkouts'] = [{
'id': operationgroup.checkout.pk,
'balance': operationgroup.checkout.balance,
}]
websocket_data['articles'] = []
# Need refresh from db cause we used update on querysets
articles_pk = [ article.pk for article in to_articles_stocks]
articles = Article.objects.values('id', 'stock').filter(pk__in=articles_pk)
for article in articles:
websocket_data['articles'].append({
'id': article['id'],
'stock': article['stock']
})
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
return JsonResponse(data)
@teamkfet_required
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('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
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
# Les balances de caisses dont il y a eu un relevé depuis la date
# de la commande ne doivent pas être modifiées
# TODO : Prendre en compte le dernier relevé où la caisse a été
# comptée et donc modifier les balance_old (et amount_error)
# des relevés suivants.
# Note : Dans le cas où un CheckoutStatement est mis à jour
# par `.save()`, amount_error est recalculé automatiquement,
# ce qui n'est pas le cas en faisant un update sur queryset
# TODO ? : Maj les balance_old de relevés pour modifier l'erreur
last_statement = (CheckoutStatement.objects
.filter(checkout=ope.group.checkout)
.order_by('at')
.last())
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
else:
to_checkouts_balances[ope.group.checkout] -= ope.amount
# Pour les stocks d'articles
# Les stocks d'articles dont il y a eu un inventaire depuis la date
# de la commande ne doivent pas être modifiés
# TODO : Prendre en compte le dernier inventaire où le stock a bien
# été compté (pas dans le cas d'une livraison).
# Note : si InventoryArticle est maj par .save(), stock_error
# 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())
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
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)
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():
(Operation.objects.filter(pk__in=opes)
.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])
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])
# Websocket data
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 = (OperationGroup.objects
.values('id','amount','is_cof').filter(pk__in=opegroups_pk))
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,
})
# Need refresh from db cause we used update on querysets
checkouts_pk = [ checkout.pk for checkout in to_checkouts_balances]
checkouts = (Checkout.objects
.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 = Article.objects.values('id', 'stock').filter(pk__in=articles_pk)
for article in articles:
websocket_data['articles'].append({
'id': article['id'],
'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
return JsonResponse(data)
@login_required
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)
# Construction de la requête (sur les opérations) pour le prefetch
queryset_prefetch = Operation.objects.select_related(
'canceled_by__trigramme', 'addcost_for__trigramme',
'article__name')
# Construction de la requête principale
opegroups = (OperationGroup.objects
.prefetch_related(Prefetch('opes', queryset = queryset_prefetch))
.select_related('on_acc__trigramme', 'valid_by__trigramme')
.order_by('at')
)
# Application des filtres
if from_date:
opegroups = opegroups.filter(at__gte=from_date)
if to_date:
opegroups = opegroups.filter(at__lt=to_date)
if checkouts:
opegroups = opegroups.filter(checkout_id__in=checkouts)
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 = {
'id' : opegroup.id,
'amount' : opegroup.amount,
'at' : opegroup.at,
'checkout_id': opegroup.checkout_id,
'is_cof' : opegroup.is_cof,
'comment' : opegroup.comment,
'opes' : [],
'on_acc__trigramme':
opegroup.on_acc and opegroup.on_acc.trigramme or None,
}
if request.user.has_perm('kfet.is_team'):
opegroup_dict['valid_by__trigramme'] = (
opegroup.valid_by and opegroup.valid_by.trigramme or None)
for ope in opegroup.opes.all():
ope_dict = {
'id' : ope.id,
'type' : ope.type,
'amount' : ope.amount,
'article_nb' : ope.article_nb,
'is_checkout' : ope.is_checkout,
'addcost_amount': ope.addcost_amount,
'canceled_at' : ope.canceled_at,
'article__name':
ope.article and ope.article.name or None,
'addcost_for__trigramme':
ope.addcost_for and ope.addcost_for.trigramme or None,
}
if request.user.has_perm('kfet.is_team'):
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 })
@teamkfet_required
def kpsul_articles_data(request):
articles = (
Article.objects
.values('id', 'name', 'price', 'stock', 'category_id', 'category__name')
.filter(is_sold=True))
return JsonResponse({ 'articles': list(articles) })
@teamkfet_required
def history(request):
data = {
'filter_form': FilterHistoryForm(),
'settings': {
'subvention_cof': Settings.SUBVENTION_COF(),
}
}
return render(request, 'kfet/history.html', data)
# -----
# Settings views
# -----
class SettingsList(ListView):
model = Settings
context_object_name = 'settings'
template_name = 'kfet/settings.html'
def get_context_data(self, **kwargs):
Settings.create_missing()
return super(SettingsList, self).get_context_data(**kwargs)
class SettingsUpdate(SuccessMessageMixin, UpdateView):
model = Settings
form_class = SettingsForm
template_name = 'kfet/settings_update.html'
success_message = 'Paramètre %(name)s mis à jour'
success_url = reverse_lazy('kfet.settings')
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('kfet.change_settings'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Creating
Settings.empty_cache()
return super(SettingsUpdate, self).form_valid(form)
# -----
# Transfer views
# -----
@teamkfet_required
def transfers(request):
transfergroups = (TransferGroup.objects
.prefetch_related('transfers')
.order_by('-at'))
return render(request, 'kfet/transfers.html', {
'transfergroups': transfergroups,
})
@teamkfet_required
def transfers_create(request):
transfer_formset = TransferFormSet(queryset=Transfer.objects.none())
return render(request, 'kfet/transfers_create.html',
{ 'transfer_formset': transfer_formset })
@teamkfet_required
def perform_transfers(request):
data = { 'errors': {}, 'transfers': [], 'transfergroup': 0 }
# Checking transfer_formset
transfer_formset = TransferFormSet(request.POST)
if not transfer_formset.is_valid():
return JsonResponse({ 'errors': list(transfer_formset.errors)}, status=400)
transfers = transfer_formset.save(commit = False)
# Initializing vars
required_perms = set(['kfet.add_transfer']) # Required perms to perform all transfers
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
for transfer in transfers:
to_accounts_balances[transfer.from_acc] -= transfer.amount
to_accounts_balances[transfer.to_acc] += transfer.amount
stop_all = False
negative_accounts = []
# Checking if ok on all accounts
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)
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)
# Creating transfer group
transfergroup = TransferGroup()
if required_perms:
transfergroup.valid_by = request.user.profile.account_kfet
comment = request.POST.get('comment', '')
transfergroup.comment = comment.strip()
with transaction.atomic():
# Updating balances accounts
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()
# Saving transfer group
transfergroup.save()
data['transfergroup'] = transfergroup.pk
# Saving all transfers with group
for transfer in transfers:
transfer.group = transfergroup
transfer.save()
data['transfers'].append(transfer.pk)
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
.select_related('by', 'order')
.annotate(nb_articles=Count('articles'))
.order_by('-at'))
template_name = 'kfet/inventory.html'
context_object_name = 'inventories'
@teamkfet_required
def inventory_create(request):
articles = (Article.objects
.select_related('category')
.order_by('category__name', 'name')
)
initial = []
for article in articles:
initial.append({
'article' : article.pk,
'stock_old': article.stock,
'name' : article.name,
'category' : article.category_id,
'category__name': article.category.name
})
cls_formset = formset_factory(
form = InventoryArticleForm,
extra = 0,
)
if request.POST:
formset = cls_formset(request.POST, initial=initial)
if not request.user.has_perm('kfet.add_inventory'):
messages.error(request, 'Permission refusée')
elif formset.is_valid():
with transaction.atomic():
articles = Article.objects.select_for_update()
inventory = Inventory()
inventory.by = request.user.profile.account_kfet
saved = False
for form in formset:
if form.cleaned_data['stock_new'] is not None:
if not saved:
inventory.save()
saved = True
article = articles.get(pk=form.cleaned_data['article'].pk)
stock_old = article.stock
stock_new = form.cleaned_data['stock_new']
InventoryArticle.objects.create(
inventory = inventory,
article = article,
stock_old = stock_old,
stock_new = stock_new)
article.stock = stock_new
article.save()
if saved:
messages.success(request, 'Inventaire créé')
return redirect('kfet.inventory')
messages.warning(request, 'Bah alors ? On a rien compté ?')
else:
messages.error(request, 'Pas marché')
else:
formset = cls_formset(initial = initial)
return render(request, 'kfet/inventory_create.html', {
'formset': formset,
})
class InventoryRead(DetailView):
model = Inventory
template_name = 'kfet/inventory_read.html'
context_object_name = 'inventory'
def get_context_data(self, **kwargs):
context = super(InventoryRead, self).get_context_data(**kwargs)
inventoryarticles = (InventoryArticle.objects
.select_related('article', 'article__category')
.filter(inventory = self.object)
.order_by('article__category__name', 'article__name'))
context['inventoryarts'] = inventoryarticles
return context
# -----
# Order views
# -----
class OrderList(ListView):
queryset = Order.objects.select_related('supplier', 'inventory')
template_name = 'kfet/order.html'
context_object_name = 'orders'
def get_context_data(self, **kwargs):
context = super(OrderList, self).get_context_data(**kwargs)
context['suppliers'] = Supplier.objects.order_by('name')
return context
@teamkfet_required
def order_create(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
articles = (Article.objects
.filter(suppliers=supplier.pk)
.select_related('category')
.order_by('category__name', 'name'))
initial = []
today = timezone.now().date()
sales_q = (Operation.objects
.select_related('group')
.filter(article__in=articles, canceled_at=None)
.values('article'))
sales_s1 = (sales_q
.filter(
group__at__gte = today-timedelta(weeks=5),
group__at__lt = today-timedelta(weeks=4))
.annotate(nb=Sum('article_nb'))
)
sales_s1 = { d['article']:d['nb'] for d in sales_s1 }
sales_s2 = (sales_q
.filter(
group__at__gte = today-timedelta(weeks=4),
group__at__lt = today-timedelta(weeks=3))
.annotate(nb=Sum('article_nb'))
)
sales_s2 = { d['article']:d['nb'] for d in sales_s2 }
sales_s3 = (sales_q
.filter(
group__at__gte = today-timedelta(weeks=3),
group__at__lt = today-timedelta(weeks=2))
.annotate(nb=Sum('article_nb'))
)
sales_s3 = { d['article']:d['nb'] for d in sales_s3 }
sales_s4 = (sales_q
.filter(
group__at__gte = today-timedelta(weeks=2),
group__at__lt = today-timedelta(weeks=1))
.annotate(nb=Sum('article_nb'))
)
sales_s4 = { d['article']:d['nb'] for d in sales_s4 }
sales_s5 = (sales_q
.filter(group__at__gte = today-timedelta(weeks=1))
.annotate(nb=Sum('article_nb'))
)
sales_s5 = { d['article']:d['nb'] for d in sales_s5 }
for article in articles:
v_s1 = sales_s1.get(article.pk, 0)
v_s2 = sales_s2.get(article.pk, 0)
v_s3 = sales_s3.get(article.pk, 0)
v_s4 = sales_s4.get(article.pk, 0)
v_s5 = sales_s5.get(article.pk, 0)
v_all = [v_s1, v_s2, v_s3, v_s4, v_s5]
v_3max = heapq.nlargest(3, v_all)
v_moy = statistics.mean(v_3max)
v_et = statistics.pstdev(v_3max, v_moy)
v_prev = v_moy + v_et
c_rec_tot = max(v_prev * 1.5 - article.stock, 0)
if article.box_capacity:
c_rec_temp = c_rec_tot / article.box_capacity
if c_rec_temp >= 10:
c_rec = round(c_rec_temp)
elif c_rec_temp > 5:
c_rec = 10
elif c_rec_temp > 2:
c_rec = 5
else:
c_rec = round(c_rec_temp)
initial.append({
'article': article.pk,
'name': article.name,
'category': article.category_id,
'category__name': article.category.name,
'stock': article.stock,
'box_capacity': article.box_capacity,
'v_s1': v_s1,
'v_s2': v_s2,
'v_s3': v_s3,
'v_s4': v_s4,
'v_s5': v_s5,
'v_moy': round(v_moy),
'v_et': round(v_et),
'v_prev': round(v_prev),
'c_rec': article.box_capacity and c_rec or round(c_rec_tot),
})
cls_formset = formset_factory(
form = OrderArticleForm,
extra = 0)
if request.POST:
formset = cls_formset(request.POST, initial=initial)
if not request.user.has_perm('kfet.add_order'):
messages.error(request, 'Permission refusée')
elif formset.is_valid():
order = Order()
order.supplier = supplier
saved = False
for form in formset:
if form.cleaned_data['quantity_ordered'] is not None:
if not saved:
order.save()
saved = True
article = articles.get(pk=form.cleaned_data['article'].pk)
q_ordered = form.cleaned_data['quantity_ordered']
if article.box_capacity:
q_ordered *= article.box_capacity
OrderArticle.objects.create(
order = order,
article = article,
quantity_ordered = q_ordered)
if saved:
messages.success(request, 'Commande créée')
return redirect('kfet.order.read', order.pk)
messages.warning(request, 'Rien commandé => Pas de commande')
else:
messages.error(request, 'Corrigez les erreurs')
else:
formset = cls_formset(initial=initial)
return render(request, 'kfet/order_create.html', {
'supplier': supplier,
'formset' : formset,
})
class OrderRead(DetailView):
model = Order
template_name = 'kfet/order_read.html'
context_object_name = 'order'
def get_context_data(self, **kwargs):
context = super(OrderRead, self).get_context_data(**kwargs)
orderarticles = (OrderArticle.objects
.select_related('article', 'article__category')
.filter(order=self.object)
.order_by('article__category__name', 'article__name')
)
context['orderarts'] = orderarticles
mail = ("Bonjour,\n\nNous voudrions pour le ##DATE## à la K-Fêt de "
"l'ENS Ulm :")
category = 0
for orderarticle in orderarticles:
if category != orderarticle.article.category:
category = orderarticle.article.category
mail += '\n'
nb = orderarticle.quantity_ordered
box = ''
if orderarticle.article.box_capacity:
nb /= orderarticle.article.box_capacity
if nb >= 2:
box = ' %ss de' % orderarticle.article.box_type
else:
box = ' %s de' % orderarticle.article.box_type
name = orderarticle.article.name.capitalize()
mail += "\n- %s%s %s" % (round(nb), box, name)
mail += ("\n\nMerci d'appeler le numéro suivant lorsque les livreurs "
"sont là : ##TELEPHONE##\nCordialement,\n##PRENOM## ##NOM## "
", pour la K-Fêt de l'ENS Ulm")
context['mail'] = mail
return context
@teamkfet_required
def order_to_inventory(request, pk):
order = get_object_or_404(Order, pk=pk)
if hasattr(order, 'inventory'):
raise Http404
articles = (Article.objects
.filter(orders=order.pk)
.select_related('category')
.prefetch_related(Prefetch('orderarticle_set',
queryset = OrderArticle.objects.filter(order=order),
to_attr = 'order'))
.prefetch_related(Prefetch('supplierarticle_set',
queryset = (SupplierArticle.objects
.filter(supplier=order.supplier)
.order_by('-at')),
to_attr = 'supplier'))
.order_by('category__name', 'name'))
initial = []
for article in articles:
initial.append({
'article': article.pk,
'name': article.name,
'category': article.category_id,
'category__name': article.category.name,
'quantity_ordered': article.order[0].quantity_ordered,
'quantity_received': article.order[0].quantity_ordered,
'price_HT': article.supplier[0].price_HT,
'TVA': article.supplier[0].TVA,
'rights': article.supplier[0].rights,
})
cls_formset = formset_factory(OrderArticleToInventoryForm, extra=0)
if request.method == 'POST':
formset = cls_formset(request.POST, initial=initial)
if not request.user.has_perm('kfet.order_to_inventory'):
messages.error(request, 'Permission refusée')
elif formset.is_valid():
with transaction.atomic():
inventory = Inventory()
inventory.order = order
inventory.by = request.user.profile.account_kfet
inventory.save()
for form in formset:
q_received = form.cleaned_data['quantity_received']
article = form.cleaned_data['article']
SupplierArticle.objects.create(
supplier = order.supplier,
article = article,
price_HT = form.cleaned_data['price_HT'],
TVA = form.cleaned_data['TVA'],
rights = form.cleaned_data['rights'])
(OrderArticle.objects
.filter(order=order, article=article)
.update(quantity_received = q_received))
InventoryArticle.objects.create(
inventory = inventory,
article = article,
stock_old = article.stock,
stock_new = article.stock + q_received)
article.stock += q_received
if q_received > 0:
article.is_sold = True
article.save()
messages.success(request, "C'est tout bon !")
return redirect('kfet.order')
else:
messages.error(request, "Corrigez les erreurs")
else:
formset = cls_formset(initial=initial)
return render(request, 'kfet/order_to_inventory.html', {
'formset': formset,
})
class SupplierUpdate(SuccessMessageMixin, UpdateView):
model = Supplier
template_name = 'kfet/supplier_form.html'
fields = ['name', 'address', 'email', 'phone', 'comment']
success_url = reverse_lazy('kfet.order')
sucess_message = 'Données fournisseur mis à jour'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('kfet.change_supplier'):
form.add_error(None, 'Permission refusée')
return self.form_invalid(form)
# Updating
return super(SupplierUpdate, self).form_valid(form)