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 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, Supplier, SupplierArticle, Inventory, InventoryArticle) 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) # 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) # 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) # 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 # ----- @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 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) @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 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 }) @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 Settings.empty_cache() return super(SettingsUpdate, self).form_valid(form) # ----- # Transfer views # ----- @permission_required('kfet.is_team') def transfers(request): transfergroups = (TransferGroup.objects .prefetch_related('transfers') .order_by('-at')) return render(request, 'kfet/transfers.html', { 'transfergroups': transfergroups, }) @permission_required('kfet.is_team') def transfers_create(request): transfer_formset = TransferFormSet(queryset=Transfer.objects.none()) return render(request, 'kfet/transfers_create.html', { 'transfer_formset': transfer_formset }) @permission_required('kfet.is_team') 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' @permission_required('kfet.is_team') def inventory_create(request): articles = (Article.objects .select_related('category') .order_by('category__name', 'name') ) initial = [] data = [] 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 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() article = articles.get(pk=form.cleaned_data['article'].pk) stock_old = article.stock stock_new = form.cleaned_data['stock_new'] stock_error = stock_new - stock_old InventoryArticle.objects.create( inventory = inventory, article = article, stock_old = stock_old, stock_new = stock_new, stock_error = stock_error) article.stock = stock_new article.save() print('ok') else: messages.error('Pas marché') else: formset = cls_formset(initial = initial) return render(request, 'kfet/inventory_create.html', { 'formset': formset, })