forked from DGNum/gestioCOF
c4fa4ea20c
- Reprise en utilisant `history.js` - Fix csrf_token sur ajax POST K-Psul - Fix annulation K-Psul - Ajouts de select_related pour économiser de la requête BDD
1129 lines
44 KiB
Python
1129 lines
44 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
|
|
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
|
|
from django.db import IntegrityError, transaction
|
|
from django.db.models import F, Sum, Prefetch
|
|
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.models import (Account, Checkout, Article, Settings, AccountNegative,
|
|
CheckoutStatement, GenericTeamToken)
|
|
from kfet.forms import *
|
|
from collections import defaultdict
|
|
from kfet import consumers
|
|
import datetime
|
|
import django_cas_ng
|
|
|
|
@login_required
|
|
def home(request):
|
|
return render(request, "kfet/base.html")
|
|
|
|
@permission_required('kfet.is_team')
|
|
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
|
|
@permission_required('kfet.is_team')
|
|
def account(request):
|
|
accounts = Account.objects.select_related('cofprofile__user').order_by('trigramme')
|
|
return render(request, "kfet/account.html", { 'accounts' : accounts })
|
|
|
|
@login_required
|
|
@permission_required('kfet.is_team')
|
|
def account_is_validandfree_ajax(request):
|
|
if not request.GET.get("trigramme", ''):
|
|
raise Http404
|
|
trigramme = request.GET.get("trigramme")
|
|
data = Account.is_validandfree(trigramme)
|
|
return JsonResponse(data)
|
|
|
|
# Account - Create
|
|
|
|
@login_required
|
|
@permission_required('kfet.is_team')
|
|
def account_create(request):
|
|
|
|
# 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
|
|
@permission_required('kfet.is_team')
|
|
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()
|
|
|
|
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
|
|
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):
|
|
model = Article
|
|
queryset = Article.objects.order_by('category', '-is_sold', 'name')
|
|
template_name = 'kfet/article.html'
|
|
context_object_name = 'articles'
|
|
|
|
# Article - Create
|
|
|
|
class ArticleCreate(SuccessMessageMixin, CreateView):
|
|
model = Article
|
|
template_name = 'kfet/article_create.html'
|
|
form_class = ArticleForm
|
|
success_message = 'Nouvel item : %(category)s - %(name)s'
|
|
|
|
# Surcharge de la validation
|
|
def form_valid(self, form):
|
|
# Checking permission
|
|
if not self.request.user.has_perm('kfet.add_article'):
|
|
form.add_error(None, 'Permission refusée')
|
|
return self.form_invalid(form)
|
|
|
|
# Creating
|
|
return super(ArticleCreate, self).form_valid(form)
|
|
|
|
# Article - Read
|
|
|
|
class ArticleRead(DetailView):
|
|
model = Article
|
|
template_name = 'kfet/article_read.html'
|
|
context_object_name = 'article'
|
|
|
|
# Article - Update
|
|
|
|
class ArticleUpdate(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)
|
|
# Updating
|
|
return super(ArticleUpdate, self).form_valid(form)
|
|
|
|
# -----
|
|
# K-Psul
|
|
# -----
|
|
|
|
@permission_required('kfet.is_team')
|
|
def kpsul(request):
|
|
data = {}
|
|
data['operationgroup_form'] = KPsulOperationGroupForm()
|
|
data['trigramme_form'] = KPsulAccountForm()
|
|
data['checkout_form'] = KPsulCheckoutForm()
|
|
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
|
|
data['operation_formset'] = operation_formset
|
|
return render(request, 'kfet/kpsul.html', data)
|
|
|
|
@permission_required('kfet.is_team')
|
|
def kpsul_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)
|
|
|
|
@permission_required('kfet.is_team')
|
|
def kpsul_account_data(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)
|
|
|
|
@permission_required('kfet.is_team')
|
|
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)
|
|
|
|
@permission_required('kfet.is_team')
|
|
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
|
|
|
|
@permission_required('kfet.is_team')
|
|
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)
|
|
|
|
# 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:
|
|
to_checkout_balance += -operation.amount
|
|
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 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 operationgroup.on_acc.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)
|
|
|
|
@permission_required('kfet.is_team')
|
|
def kpsul_cancel_operations(request):
|
|
# Pour la réponse
|
|
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
|
|
|
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
|
try:
|
|
# Set pour virer les doublons
|
|
opes_post = set(map(int, filter(None, request.POST.getlist('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 ? : 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
|
|
if ope.article and ope.article_nb:
|
|
to_articles_stocks[ope.article] += ope.article_nb
|
|
|
|
if not opes:
|
|
data['warnings']['already_canceled'] = opes_already_canceled
|
|
return JsonResponse(data)
|
|
|
|
# Checking permissions or stop
|
|
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
|
|
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
|
|
for account in to_accounts_balances:
|
|
(perms, stop) = account.perms_to_perform_operation(
|
|
amount = to_accounts_balances[account],
|
|
overdraft_duration_max = overdraft_duration_max,
|
|
overdraft_amount_max = overdraft_amount_max)
|
|
required_perms |= perms
|
|
stop_all = stop_all or stop
|
|
|
|
if stop_all or not request.user.has_perms(required_perms):
|
|
missing_perms = get_missing_perms(required_perms, request.user)
|
|
if missing_perms:
|
|
data['errors']['missing_perms'] = missing_perms
|
|
if stop_all:
|
|
data['errors']['negative'] = True
|
|
return JsonResponse(data, status=403)
|
|
|
|
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 })
|
|
|
|
@permission_required('kfet.is_team')
|
|
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) })
|
|
|
|
@permission_required('kfet.is_team')
|
|
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
|
|
return super(SettingsUpdate, self).form_valid(form)
|
|
|