forked from DGNum/gestioCOF
e927ad5801
- Un accès sur une vue protégée nécessitant d'être de l'équipe envoie vers la page de connexion si l'utilisateur n'est pas connecté
1693 lines
65 KiB
Python
1693 lines
65 KiB
Python
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 heapq
|
|
import statistics
|
|
|
|
@login_required
|
|
def home(request):
|
|
return render(request, "kfet/base.html")
|
|
|
|
@teamkfet_required
|
|
def login_genericteam(request):
|
|
profile, _ = CofProfile.objects.get_or_create(user=request.user)
|
|
logout_cas = ''
|
|
if profile.login_clipper:
|
|
logout_cas = django_cas_ng.views.logout(request)
|
|
|
|
token = GenericTeamToken.objects.create(token=get_random_string(50))
|
|
user = authenticate(username="kfet_genericteam", token=token.token)
|
|
login(request, user)
|
|
|
|
if logout_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(request):
|
|
|
|
# A envoyer au template
|
|
data_template = {
|
|
'account_trigramme_form': AccountTriForm(),
|
|
}
|
|
|
|
# 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
|
|
|
|
# Peuplement des forms
|
|
username = request.POST.get('username')
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
(cof, _) = CofProfile.objects.get_or_create(user=user)
|
|
user_form = UserForm(request.POST, instance=user)
|
|
cof_form = CofForm(request.POST, instance=cof)
|
|
except User.DoesNotExist:
|
|
user_form = UserForm(request.POST)
|
|
cof_form = CofForm(request.POST)
|
|
trigramme_form = AccountTriForm(request.POST)
|
|
account_form = AccountNoTriForm(request.POST)
|
|
|
|
if all((user_form.is_valid(), cof_form.is_valid(),
|
|
trigramme_form.is_valid(), account_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)
|
|
|
|
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)
|
|
except Account.UserHasAccount as e:
|
|
messages.error(request, \
|
|
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme)
|
|
else:
|
|
messages.error(request, user_form.errors)
|
|
messages.error(request, cof_form.errors)
|
|
messages.error(request, trigramme_form.errors)
|
|
messages.error(request, account_form.errors)
|
|
|
|
return render(request, "kfet/account_create.html", data_template)
|
|
|
|
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
|
|
|
|
@login_required
|
|
@teamkfet_required
|
|
def account_create_ajax(request, username=None, login_clipper=None):
|
|
user = None
|
|
if login_clipper:
|
|
# à 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
|
|
login_clipper = None
|
|
except User.DoesNotExist:
|
|
# Clipper (sans user déjà existant)
|
|
|
|
# UserForm - Prefill + Création
|
|
user_initial_data = {
|
|
'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_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)
|
|
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()
|
|
# Protection (read-only) des champs username et login_clipper
|
|
account_form_set_readonly_fields(user_form, cof_form)
|
|
elif not login_clipper:
|
|
# connaît pas du tout, faut tout remplir
|
|
user_form = UserForm()
|
|
cof_form = CofForm()
|
|
account_form = AccountNoTriForm()
|
|
cof_form.fields['login_clipper'].widget.attrs['readonly'] = True
|
|
cof_form.fields['is_cof'].widget.attrs['disabled'] = True
|
|
|
|
return render(request, "kfet/account_create_form.html", {
|
|
'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.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)
|
|
if hasattr(account, 'negative'):
|
|
negative_form = AccountNegativeForm(instance=account.negative)
|
|
else:
|
|
negative_form = None
|
|
else:
|
|
user_form = UserRestrictForm(instance=account.user)
|
|
account_form = None
|
|
cof_form = None
|
|
group_form = None
|
|
negative_form = None
|
|
|
|
if request.method == "POST":
|
|
# Update attempt
|
|
success = False
|
|
missing_perm = False
|
|
|
|
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)
|
|
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()):
|
|
data = {}
|
|
# Fill data for Account.save()
|
|
put_cleaned_data_in_dict(data, user_form)
|
|
put_cleaned_data_in_dict(data, cof_form)
|
|
print(vars(account.negative))
|
|
|
|
# Updating
|
|
account_form.save(data = data)
|
|
|
|
# Checking perm to manage perms
|
|
if (request.user.has_perm('kfet.manage_perms')
|
|
and group_form.is_valid()):
|
|
group_form.save()
|
|
print(vars(account.negative))
|
|
|
|
# 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_old - balance_offset_new
|
|
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)
|
|
elif not request.user.has_perm('kfet.change_account'):
|
|
missing_perm = True
|
|
|
|
if request.user == account.user:
|
|
missing_perm = False
|
|
user_form = UserRestrictForm(request.POST, instance=account.user)
|
|
|
|
if user_form.is_valid():
|
|
user_form.save()
|
|
success = True
|
|
messages.success(request, 'Vos informations ont été mises à jour')
|
|
|
|
if missing_perm:
|
|
messages.error('Permission refusée')
|
|
if success:
|
|
return redirect('kfet.account.read', account.trigramme)
|
|
else:
|
|
messages.error('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,
|
|
})
|
|
|
|
@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'))
|
|
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
|
|
.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()
|
|
data['checkout_form'] = KPsulCheckoutForm()
|
|
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)
|
|
try:
|
|
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())
|
|
except Checkout.DoesNotExist:
|
|
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'] = True
|
|
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 = (ArticleInventory.objects
|
|
.select_related('inventory')
|
|
.filter(article=ope.article)
|
|
.order_by('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)
|
|
|
|
# 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_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)
|
|
|
|
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)
|
|
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)
|
|
|
|
# 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() # 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
|
|
|
|
# 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_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)
|
|
|
|
# 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)
|
|
|
|
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('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'):
|
|
message.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)
|