2018-10-06 12:35:49 +02:00
|
|
|
import heapq
|
|
|
|
import statistics
|
|
|
|
from collections import defaultdict
|
2021-02-10 21:32:44 +01:00
|
|
|
from datetime import datetime, timedelta
|
2018-10-06 12:35:49 +02:00
|
|
|
from decimal import Decimal
|
2022-05-20 12:08:59 +02:00
|
|
|
from typing import List, Tuple
|
2017-04-02 17:03:20 +02:00
|
|
|
from urllib.parse import urlencode
|
2016-09-01 00:45:44 +02:00
|
|
|
|
2022-06-27 15:34:24 +02:00
|
|
|
from asgiref.sync import async_to_sync
|
|
|
|
from channels.layers import get_channel_layer
|
2021-02-20 20:59:54 +01:00
|
|
|
from django.conf import settings
|
2016-08-04 05:21:04 +02:00
|
|
|
from django.contrib import messages
|
2017-10-24 17:56:14 +02:00
|
|
|
from django.contrib.auth.decorators import login_required, permission_required
|
2019-05-24 14:30:00 +02:00
|
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
2018-10-06 12:35:49 +02:00
|
|
|
from django.contrib.auth.models import Permission, User
|
|
|
|
from django.contrib.messages.views import SuccessMessageMixin
|
2022-01-05 10:48:04 +01:00
|
|
|
from django.core.exceptions import SuspiciousOperation
|
2017-01-07 16:28:53 +01:00
|
|
|
from django.db import transaction
|
2020-09-11 15:21:29 +02:00
|
|
|
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
|
2021-02-28 02:02:31 +01:00
|
|
|
from django.forms import ValidationError, formset_factory
|
2021-02-20 15:46:44 +01:00
|
|
|
from django.http import (
|
|
|
|
Http404,
|
|
|
|
HttpResponseBadRequest,
|
|
|
|
HttpResponseForbidden,
|
|
|
|
JsonResponse,
|
|
|
|
)
|
2018-10-06 12:35:49 +02:00
|
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
2019-03-19 10:18:56 +01:00
|
|
|
from django.urls import reverse, reverse_lazy
|
2016-08-08 07:44:05 +02:00
|
|
|
from django.utils import timezone
|
2016-12-24 12:33:04 +01:00
|
|
|
from django.utils.decorators import method_decorator
|
2018-10-06 12:35:49 +02:00
|
|
|
from django.views.generic import DetailView, FormView, ListView, TemplateView
|
|
|
|
from django.views.generic.detail import BaseDetailView
|
2019-05-24 14:30:00 +02:00
|
|
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
2017-05-30 20:44:30 +02:00
|
|
|
|
2016-12-25 02:02:22 +01:00
|
|
|
from gestioncof.models import CofProfile
|
2022-06-27 15:34:24 +02:00
|
|
|
from kfet import KFET_DELETED_TRIGRAMME
|
2019-11-21 01:18:38 +01:00
|
|
|
from kfet.auth.decorators import kfet_password_auth
|
2020-07-18 16:05:16 +02:00
|
|
|
from kfet.autocomplete import kfet_account_only_autocomplete, kfet_autocomplete
|
2017-04-03 20:32:16 +02:00
|
|
|
from kfet.config import kfet_config
|
2016-08-31 02:52:13 +02:00
|
|
|
from kfet.decorators import teamkfet_required
|
2017-01-07 16:28:53 +01:00
|
|
|
from kfet.forms import (
|
2018-10-06 12:35:49 +02:00
|
|
|
AccountForm,
|
2021-02-23 23:54:17 +01:00
|
|
|
AccountFrozenForm,
|
2018-10-06 12:35:49 +02:00
|
|
|
AccountNoTriForm,
|
|
|
|
AccountPwdForm,
|
2020-09-16 17:16:49 +02:00
|
|
|
AccountStatForm,
|
2018-10-06 12:35:49 +02:00
|
|
|
AccountTriForm,
|
|
|
|
AddcostForm,
|
|
|
|
ArticleForm,
|
|
|
|
ArticleRestrictForm,
|
|
|
|
CategoryForm,
|
|
|
|
CheckoutForm,
|
|
|
|
CheckoutRestrictForm,
|
|
|
|
CheckoutStatementCreateForm,
|
|
|
|
CheckoutStatementUpdateForm,
|
|
|
|
CofForm,
|
|
|
|
FilterHistoryForm,
|
|
|
|
InventoryArticleForm,
|
|
|
|
KFetConfigForm,
|
|
|
|
KPsulAccountForm,
|
|
|
|
KPsulCheckoutForm,
|
|
|
|
KPsulOperationFormSet,
|
|
|
|
KPsulOperationGroupForm,
|
|
|
|
OrderArticleForm,
|
|
|
|
OrderArticleToInventoryForm,
|
2020-09-16 17:16:49 +02:00
|
|
|
StatScaleForm,
|
2018-10-06 12:35:49 +02:00
|
|
|
TransferFormSet,
|
|
|
|
UserForm,
|
|
|
|
UserGroupForm,
|
2019-06-03 19:34:41 +02:00
|
|
|
UserInfoForm,
|
2018-10-06 12:35:49 +02:00
|
|
|
)
|
|
|
|
from kfet.models import (
|
|
|
|
Account,
|
|
|
|
AccountNegative,
|
|
|
|
Article,
|
|
|
|
ArticleCategory,
|
|
|
|
Checkout,
|
|
|
|
CheckoutStatement,
|
|
|
|
Inventory,
|
|
|
|
InventoryArticle,
|
|
|
|
Operation,
|
|
|
|
OperationGroup,
|
|
|
|
Order,
|
|
|
|
OrderArticle,
|
|
|
|
Supplier,
|
|
|
|
SupplierArticle,
|
|
|
|
Transfer,
|
|
|
|
TransferGroup,
|
|
|
|
)
|
2020-09-16 17:16:49 +02:00
|
|
|
from kfet.statistic import SCALE_DICT, DayScale, MonthScale, WeekScale, scale_url_params
|
2020-07-01 22:29:07 +02:00
|
|
|
from shared.views import AutocompleteView
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2019-05-24 16:16:20 +02:00
|
|
|
from .auth import KFET_GENERIC_TRIGRAMME
|
2017-09-13 01:57:31 +02:00
|
|
|
from .auth.views import ( # noqa
|
2018-10-06 12:35:49 +02:00
|
|
|
AccountGroupCreate,
|
|
|
|
AccountGroupUpdate,
|
|
|
|
account_group,
|
|
|
|
login_generic,
|
2017-09-13 01:57:31 +02:00
|
|
|
)
|
2016-08-20 21:08:33 +02:00
|
|
|
|
2016-08-20 19:35:45 +02:00
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
def put_cleaned_data_in_dict(dict, form):
|
|
|
|
for field in form.cleaned_data:
|
|
|
|
dict[field] = form.cleaned_data[field]
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# -----
|
|
|
|
# Account views
|
|
|
|
# -----
|
|
|
|
|
|
|
|
# Account - General
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
@login_required
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-03 04:38:54 +02:00
|
|
|
def account(request):
|
2018-10-06 12:35:49 +02:00
|
|
|
accounts = Account.objects.select_related("cofprofile__user").order_by("trigramme")
|
|
|
|
return render(request, "kfet/account.html", {"accounts": accounts})
|
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
@login_required
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-04 05:21:04 +02:00
|
|
|
def account_is_validandfree_ajax(request):
|
2018-10-06 12:35:49 +02:00
|
|
|
if not request.GET.get("trigramme", ""):
|
2016-08-04 05:21:04 +02:00
|
|
|
raise Http404
|
|
|
|
trigramme = request.GET.get("trigramme")
|
|
|
|
data = Account.is_validandfree(trigramme)
|
2016-08-06 22:19:52 +02:00
|
|
|
return JsonResponse(data)
|
2016-08-04 05:21:04 +02:00
|
|
|
|
2016-09-05 07:31:54 +02:00
|
|
|
|
|
|
|
# Account - Create
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
@login_required
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-03 04:38:54 +02:00
|
|
|
def account_create(request):
|
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
# Enregistrement
|
|
|
|
if request.method == "POST":
|
2016-09-01 05:01:59 +02:00
|
|
|
trigramme_form = AccountTriForm(request.POST)
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
# Peuplement des forms
|
2018-10-06 12:35:49 +02:00
|
|
|
username = request.POST.get("username")
|
|
|
|
login_clipper = request.POST.get("login_clipper")
|
2016-09-01 05:01:59 +02:00
|
|
|
|
|
|
|
forms = get_account_create_forms(
|
2018-10-06 12:35:49 +02:00
|
|
|
request, username=username, login_clipper=login_clipper
|
|
|
|
)
|
2016-09-01 05:01:59 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
account_form = forms["account_form"]
|
|
|
|
cof_form = forms["cof_form"]
|
|
|
|
user_form = forms["user_form"]
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if all(
|
|
|
|
(
|
|
|
|
user_form.is_valid(),
|
|
|
|
cof_form.is_valid(),
|
|
|
|
trigramme_form.is_valid(),
|
|
|
|
account_form.is_valid(),
|
|
|
|
)
|
|
|
|
):
|
2016-09-01 05:01:59 +02:00
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not request.user.has_perm("kfet.add_account"):
|
2021-02-20 19:18:21 +01:00
|
|
|
messages.error(
|
|
|
|
request, "Permission refusée", extra_tags="permission-denied"
|
|
|
|
)
|
2016-09-01 05:01:59 +02:00
|
|
|
else:
|
|
|
|
data = {}
|
|
|
|
# Fill data for Account.save()
|
|
|
|
put_cleaned_data_in_dict(data, user_form)
|
|
|
|
put_cleaned_data_in_dict(data, cof_form)
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2016-09-01 05:01:59 +02:00
|
|
|
try:
|
2018-10-06 12:35:49 +02:00
|
|
|
account = trigramme_form.save(data=data)
|
2016-09-01 05:01:59 +02:00
|
|
|
account_form = AccountNoTriForm(request.POST, instance=account)
|
|
|
|
account_form.save()
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.success(request, "Compte créé : %s" % account.trigramme)
|
|
|
|
return redirect("kfet.account.create")
|
2016-09-01 05:01:59 +02:00
|
|
|
except Account.UserHasAccount as e:
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.error(
|
|
|
|
request,
|
|
|
|
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme,
|
|
|
|
)
|
2016-09-01 05:01:59 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
initial = {"trigramme": request.GET.get("trigramme", "")}
|
|
|
|
trigramme_form = AccountTriForm(initial=initial)
|
2016-09-01 05:01:59 +02:00
|
|
|
account_form = None
|
|
|
|
cof_form = None
|
|
|
|
user_form = None
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"kfet/account_create.html",
|
|
|
|
{
|
|
|
|
"trigramme_form": trigramme_form,
|
|
|
|
"account_form": account_form,
|
|
|
|
"cof_form": cof_form,
|
|
|
|
"user_form": user_form,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
def account_form_set_readonly_fields(user_form, cof_form):
|
2018-10-06 12:35:49 +02:00
|
|
|
user_form.fields["username"].widget.attrs["readonly"] = True
|
2020-09-19 19:14:44 +02:00
|
|
|
user_form.fields["first_name"].widget.attrs["readonly"] = True
|
|
|
|
user_form.fields["last_name"].widget.attrs["readonly"] = True
|
|
|
|
user_form.fields["email"].widget.attrs["readonly"] = True
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form.fields["login_clipper"].widget.attrs["readonly"] = True
|
2020-09-19 19:14:44 +02:00
|
|
|
cof_form.fields["departement"].widget.attrs["readonly"] = True
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form.fields["is_cof"].widget.attrs["disabled"] = True
|
|
|
|
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
def get_account_create_forms(
|
|
|
|
request=None, username=None, login_clipper=None, fullname=None
|
|
|
|
):
|
2016-08-02 10:40:46 +02:00
|
|
|
user = None
|
2016-12-25 02:02:22 +01:00
|
|
|
clipper = False
|
2016-09-06 19:49:28 +02:00
|
|
|
if login_clipper and (login_clipper == username or not username):
|
2016-08-02 10:40:46 +02:00
|
|
|
# à partir d'un clipper
|
2016-08-15 01:48:22 +02:00
|
|
|
# le user associé à ce clipper ne devrait pas encore exister
|
2016-12-25 02:02:22 +01:00
|
|
|
clipper = True
|
2016-08-02 10:40:46 +02:00
|
|
|
try:
|
|
|
|
# Vérification que clipper ne soit pas déjà dans User
|
2016-08-03 04:38:54 +02:00
|
|
|
user = User.objects.get(username=login_clipper)
|
2016-08-02 10:40:46 +02:00
|
|
|
# Ici, on nous a menti, le user existe déjà
|
|
|
|
username = user.username
|
2016-12-25 02:02:22 +01:00
|
|
|
clipper = False
|
2016-08-02 10:40:46 +02:00
|
|
|
except User.DoesNotExist:
|
|
|
|
# Clipper (sans user déjà existant)
|
|
|
|
|
2016-09-01 05:01:59 +02:00
|
|
|
# UserForm - Prefill
|
|
|
|
user_initial = {
|
2018-10-06 12:35:49 +02:00
|
|
|
"username": login_clipper,
|
|
|
|
"email": "%s@clipper.ens.fr" % login_clipper,
|
|
|
|
}
|
2016-12-25 02:02:22 +01:00
|
|
|
if fullname:
|
2016-08-02 10:40:46 +02:00
|
|
|
# Prefill du nom et prénom
|
2016-12-25 02:02:22 +01:00
|
|
|
names = fullname.split()
|
2016-08-02 10:40:46 +02:00
|
|
|
# Le premier, c'est le prénom
|
2018-10-06 12:35:49 +02:00
|
|
|
user_initial["first_name"] = names[0]
|
2016-08-02 10:40:46 +02:00
|
|
|
if len(names) > 1:
|
|
|
|
# Si d'autres noms -> tous dans le nom de famille
|
2018-10-06 12:35:49 +02:00
|
|
|
user_initial["last_name"] = " ".join(names[1:])
|
2016-09-01 05:01:59 +02:00
|
|
|
# CofForm - Prefill
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_initial = {"login_clipper": login_clipper}
|
2016-09-01 05:01:59 +02:00
|
|
|
|
|
|
|
# Form créations
|
|
|
|
if request:
|
2017-09-04 14:50:12 +02:00
|
|
|
user_form = UserForm(request.POST, initial=user_initial)
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form = CofForm(request.POST, initial=cof_initial)
|
2016-09-01 05:01:59 +02:00
|
|
|
else:
|
2017-09-04 14:50:12 +02:00
|
|
|
user_form = UserForm(initial=user_initial)
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form = CofForm(initial=cof_initial)
|
2016-08-02 10:40:46 +02:00
|
|
|
|
|
|
|
# Protection (read-only) des champs username et login_clipper
|
2016-08-03 04:38:54 +02:00
|
|
|
account_form_set_readonly_fields(user_form, cof_form)
|
2016-09-01 15:03:33 +02:00
|
|
|
if username and not clipper:
|
2016-09-01 05:01:59 +02:00
|
|
|
try:
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
# le user existe déjà
|
|
|
|
# récupération du profil cof
|
|
|
|
(cof, _) = CofProfile.objects.get_or_create(user=user)
|
|
|
|
# UserForm + CofForm - Création à partir des instances existantes
|
|
|
|
if request:
|
2018-10-06 12:35:49 +02:00
|
|
|
user_form = UserForm(request.POST, instance=user)
|
|
|
|
cof_form = CofForm(request.POST, instance=cof)
|
2016-09-01 05:01:59 +02:00
|
|
|
else:
|
|
|
|
user_form = UserForm(instance=user)
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form = CofForm(instance=cof)
|
2016-09-01 05:01:59 +02:00
|
|
|
# Protection (read-only) des champs username, login_clipper et is_cof
|
|
|
|
account_form_set_readonly_fields(user_form, cof_form)
|
|
|
|
except User.DoesNotExist:
|
|
|
|
# le username donnée n'existe pas -> Création depuis rien
|
|
|
|
# (éventuellement en cours avec erreurs précédemment)
|
|
|
|
pass
|
|
|
|
if not user and not clipper:
|
2016-08-02 10:40:46 +02:00
|
|
|
# connaît pas du tout, faut tout remplir
|
2016-09-01 05:01:59 +02:00
|
|
|
if request:
|
|
|
|
user_form = UserForm(request.POST)
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form = CofForm(request.POST)
|
2016-09-01 05:01:59 +02:00
|
|
|
else:
|
|
|
|
user_form = UserForm()
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form = CofForm()
|
2016-09-01 05:01:59 +02:00
|
|
|
# mais on laisse le username en écriture
|
2018-10-06 12:35:49 +02:00
|
|
|
cof_form.fields["login_clipper"].widget.attrs["readonly"] = True
|
|
|
|
cof_form.fields["is_cof"].widget.attrs["disabled"] = True
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2016-09-01 05:01:59 +02:00
|
|
|
if request:
|
|
|
|
account_form = AccountNoTriForm(request.POST)
|
|
|
|
else:
|
|
|
|
account_form = AccountNoTriForm()
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
return {"account_form": account_form, "cof_form": cof_form, "user_form": user_form}
|
2016-09-01 05:01:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
@login_required
|
|
|
|
@teamkfet_required
|
2018-10-06 12:35:49 +02:00
|
|
|
def account_create_ajax(request, username=None, login_clipper=None, fullname=None):
|
2016-12-25 02:02:22 +01:00
|
|
|
forms = get_account_create_forms(
|
2018-10-06 12:35:49 +02:00
|
|
|
request=None, username=username, login_clipper=login_clipper, fullname=fullname
|
|
|
|
)
|
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"kfet/account_create_form.html",
|
|
|
|
{
|
|
|
|
"account_form": forms["account_form"],
|
|
|
|
"cof_form": forms["cof_form"],
|
|
|
|
"user_form": forms["user_form"],
|
|
|
|
},
|
|
|
|
)
|
2016-08-02 10:40:46 +02:00
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# Account - Read
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
@login_required
|
|
|
|
def account_read(request, trigramme):
|
2017-04-06 18:23:27 +02:00
|
|
|
account = get_object_or_404(Account, trigramme=trigramme)
|
2016-08-03 04:38:54 +02:00
|
|
|
|
|
|
|
# Checking permissions
|
2017-05-30 20:44:30 +02:00
|
|
|
if not account.readable or (
|
2018-10-06 12:35:49 +02:00
|
|
|
not request.user.has_perm("kfet.is_team") and request.user != account.user
|
|
|
|
):
|
2019-10-05 01:25:36 +02:00
|
|
|
raise Http404
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2017-04-06 18:23:27 +02:00
|
|
|
addcosts = (
|
2018-10-06 12:35:49 +02:00
|
|
|
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}
|
2017-04-06 18:23:27 +02:00
|
|
|
)
|
2016-08-23 18:15:41 +02:00
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# Account - Update
|
|
|
|
|
2017-01-07 16:28:53 +01:00
|
|
|
|
2021-02-20 15:46:44 +01:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-03 04:38:54 +02:00
|
|
|
def account_update(request, trigramme):
|
2016-08-15 01:48:22 +02:00
|
|
|
account = get_object_or_404(Account, trigramme=trigramme)
|
2016-08-03 04:38:54 +02:00
|
|
|
|
|
|
|
# Checking permissions
|
2021-02-20 15:46:44 +01:00
|
|
|
if not account.editable:
|
|
|
|
# Plus de leak de trigramme !
|
|
|
|
return HttpResponseForbidden
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2019-06-03 20:34:25 +02:00
|
|
|
user_info_form = UserInfoForm(instance=account.user)
|
2021-02-20 15:46:44 +01:00
|
|
|
account_form = AccountForm(instance=account)
|
2021-02-23 23:54:17 +01:00
|
|
|
group_form = UserGroupForm(instance=account.user)
|
|
|
|
frozen_form = AccountFrozenForm(request.POST, instance=account)
|
2021-02-20 15:46:44 +01:00
|
|
|
pwd_form = AccountPwdForm()
|
2021-02-23 23:54:17 +01:00
|
|
|
|
2016-08-03 04:38:54 +02:00
|
|
|
if request.method == "POST":
|
2021-02-20 15:46:44 +01:00
|
|
|
self_update = request.user == account.user
|
|
|
|
account_form = AccountForm(request.POST, instance=account)
|
|
|
|
group_form = UserGroupForm(request.POST, instance=account.user)
|
2021-02-23 23:54:17 +01:00
|
|
|
frozen_form = AccountFrozenForm(request.POST, instance=account)
|
2021-02-20 15:46:44 +01:00
|
|
|
pwd_form = AccountPwdForm(request.POST, account=account)
|
2016-08-21 02:53:35 +02:00
|
|
|
|
2021-02-20 15:46:44 +01:00
|
|
|
forms = []
|
|
|
|
warnings = []
|
2016-08-21 02:53:35 +02:00
|
|
|
|
2021-02-20 15:46:44 +01:00
|
|
|
if self_update or request.user.has_perm("kfet.change_account"):
|
|
|
|
forms.append(account_form)
|
|
|
|
elif account_form.has_changed():
|
|
|
|
warnings.append("compte")
|
|
|
|
|
|
|
|
if request.user.has_perm("kfet.manage_perms"):
|
|
|
|
forms.append(group_form)
|
2021-02-23 23:54:17 +01:00
|
|
|
forms.append(frozen_form)
|
2021-02-20 15:46:44 +01:00
|
|
|
elif group_form.has_changed():
|
|
|
|
warnings.append("statut d'équipe")
|
|
|
|
|
|
|
|
# Il ne faut pas valider `pwd_form` si elle est inchangée
|
|
|
|
if pwd_form.has_changed():
|
|
|
|
if self_update or request.user.has_perm("kfet.change_account_password"):
|
|
|
|
forms.append(pwd_form)
|
|
|
|
else:
|
|
|
|
warnings.append("mot de passe")
|
|
|
|
|
|
|
|
# Updating account info
|
|
|
|
if forms == []:
|
2017-01-07 16:28:53 +01:00
|
|
|
messages.error(
|
2021-02-20 19:18:21 +01:00
|
|
|
request,
|
|
|
|
"Informations non mises à jour : permission refusée",
|
|
|
|
extra_tags="permission-denied",
|
2018-10-06 12:35:49 +02:00
|
|
|
)
|
2021-02-20 15:46:44 +01:00
|
|
|
else:
|
|
|
|
if all(form.is_valid() for form in forms):
|
|
|
|
for form in forms:
|
|
|
|
form.save()
|
|
|
|
|
|
|
|
if len(warnings):
|
|
|
|
messages.warning(
|
|
|
|
request,
|
|
|
|
"Permissions insuffisantes pour modifier"
|
|
|
|
" les informations suivantes : {}.".format(", ".join(warnings)),
|
|
|
|
)
|
|
|
|
if self_update:
|
|
|
|
messages.success(request, "Vos informations ont été mises à jour !")
|
|
|
|
else:
|
|
|
|
messages.success(
|
|
|
|
request,
|
|
|
|
"Informations du compte %s mises à jour" % account.trigramme,
|
|
|
|
)
|
|
|
|
|
|
|
|
return redirect("kfet.account.read", account.trigramme)
|
|
|
|
else:
|
|
|
|
messages.error(
|
|
|
|
request, "Informations non mises à jour : corrigez les erreurs"
|
|
|
|
)
|
2016-08-03 04:38:54 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"kfet/account_update.html",
|
|
|
|
{
|
2019-06-03 19:34:41 +02:00
|
|
|
"user_info_form": user_info_form,
|
2018-10-06 12:35:49 +02:00
|
|
|
"account": account,
|
|
|
|
"account_form": account_form,
|
2021-03-17 21:01:55 +01:00
|
|
|
"frozen_form": frozen_form,
|
2018-10-06 12:35:49 +02:00
|
|
|
"group_form": group_form,
|
|
|
|
"pwd_form": pwd_form,
|
|
|
|
},
|
|
|
|
)
|
2016-08-04 05:21:04 +02:00
|
|
|
|
2021-02-20 15:46:44 +01:00
|
|
|
|
|
|
|
# Account - Delete
|
2019-05-24 14:30:00 +02:00
|
|
|
|
|
|
|
|
2019-05-24 18:35:04 +02:00
|
|
|
class AccountDelete(PermissionRequiredMixin, DeleteView):
|
2019-05-24 14:30:00 +02:00
|
|
|
model = Account
|
|
|
|
slug_field = "trigramme"
|
|
|
|
slug_url_kwarg = "trigramme"
|
|
|
|
success_url = reverse_lazy("kfet.account")
|
|
|
|
success_message = "Compte supprimé avec succès !"
|
|
|
|
permission_required = "kfet.delete_account"
|
|
|
|
|
2019-06-03 22:59:43 +02:00
|
|
|
http_method_names = ["post"]
|
2019-05-24 14:30:00 +02:00
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
2019-06-03 23:00:10 +02:00
|
|
|
if self.object.balance >= 0.01:
|
2019-05-24 14:30:00 +02:00
|
|
|
messages.error(
|
|
|
|
request,
|
|
|
|
"Impossible de supprimer un compte "
|
|
|
|
"avec une balance strictement positive !",
|
|
|
|
)
|
|
|
|
return redirect("kfet.account.read", self.object.trigramme)
|
|
|
|
|
2019-05-24 16:16:20 +02:00
|
|
|
if self.object.trigramme in [
|
|
|
|
"LIQ",
|
|
|
|
KFET_GENERIC_TRIGRAMME,
|
|
|
|
KFET_DELETED_TRIGRAMME,
|
|
|
|
"#13",
|
|
|
|
]:
|
|
|
|
messages.error(request, "Impossible de supprimer un trigramme protégé !")
|
|
|
|
return redirect("kfet.account.read", self.object.trigramme)
|
|
|
|
|
2019-05-24 14:30:00 +02:00
|
|
|
# SuccessMessageMixin does not work with DeleteView, see :
|
|
|
|
# https://code.djangoproject.com/ticket/21926
|
|
|
|
messages.success(request, self.success_message)
|
|
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
2017-04-06 19:25:23 +02:00
|
|
|
|
2016-08-23 20:31:31 +02:00
|
|
|
class AccountNegativeList(ListView):
|
2021-02-28 01:59:43 +01:00
|
|
|
queryset = (
|
|
|
|
AccountNegative.objects.select_related("account", "account__cofprofile__user")
|
|
|
|
.filter(account__balance__lt=0)
|
|
|
|
.exclude(account__trigramme="#13")
|
|
|
|
)
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/account_negative.html"
|
|
|
|
context_object_name = "negatives"
|
2016-08-23 20:31:31 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2021-02-18 17:57:59 +01:00
|
|
|
balances = (neg.account.balance for neg in self.object_list)
|
|
|
|
context["negatives_sum"] = sum(balances)
|
2016-08-23 20:31:31 +02:00
|
|
|
return context
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# -----
|
|
|
|
# Checkout views
|
|
|
|
# -----
|
|
|
|
|
|
|
|
# Checkout - General
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
class CheckoutList(ListView):
|
2018-10-06 12:35:49 +02:00
|
|
|
model = Checkout
|
|
|
|
template_name = "kfet/checkout.html"
|
|
|
|
context_object_name = "checkouts"
|
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
|
|
|
|
# Checkout - Create
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2019-11-21 01:18:38 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-04 05:21:04 +02:00
|
|
|
class CheckoutCreate(SuccessMessageMixin, CreateView):
|
2018-10-06 12:35:49 +02:00
|
|
|
model = Checkout
|
|
|
|
template_name = "kfet/checkout_create.html"
|
|
|
|
form_class = CheckoutForm
|
|
|
|
success_message = "Nouvelle caisse : %(name)s"
|
2016-08-04 05:21:04 +02:00
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.add_checkout"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-22 02:52:59 +02:00
|
|
|
return self.form_invalid(form)
|
2016-08-30 17:24:11 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# Creating
|
|
|
|
form.instance.created_by = self.request.user.profile.account_kfet
|
2018-01-16 15:59:17 +01:00
|
|
|
form.save()
|
2016-08-30 17:24:11 +02:00
|
|
|
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-04 05:21:04 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# Checkout - Read
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
class CheckoutRead(DetailView):
|
2016-08-04 08:23:34 +02:00
|
|
|
model = Checkout
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/checkout_read.html"
|
|
|
|
context_object_name = "checkout"
|
2016-08-04 05:21:04 +02:00
|
|
|
|
2016-08-23 03:00:09 +02:00
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
context["statements"] = context["checkout"].statements.order_by("-at")
|
2016-08-23 03:00:09 +02:00
|
|
|
return context
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 05:21:04 +02:00
|
|
|
# Checkout - Update
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2019-12-02 20:41:19 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-04 05:21:04 +02:00
|
|
|
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
|
2018-10-06 12:35:49 +02:00
|
|
|
model = Checkout
|
|
|
|
template_name = "kfet/checkout_update.html"
|
|
|
|
form_class = CheckoutRestrictForm
|
|
|
|
success_message = "Informations mises à jour pour la caisse : %(name)s"
|
2016-08-04 05:21:04 +02:00
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_checkout"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-22 02:52:59 +02:00
|
|
|
return self.form_invalid(form)
|
2016-08-04 05:21:04 +02:00
|
|
|
# Updating
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-04 08:23:34 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-11 15:14:23 +02:00
|
|
|
# -----
|
|
|
|
# Checkout Statement views
|
|
|
|
# -----
|
|
|
|
|
|
|
|
# Checkout Statement - General
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-11 15:14:23 +02:00
|
|
|
class CheckoutStatementList(ListView):
|
2018-10-06 12:35:49 +02:00
|
|
|
model = CheckoutStatement
|
|
|
|
queryset = CheckoutStatement.objects.order_by("-at")
|
|
|
|
template_name = "kfet/checkoutstatement.html"
|
|
|
|
context_object_name = "checkoutstatements"
|
|
|
|
|
2016-08-11 15:14:23 +02:00
|
|
|
|
|
|
|
# Checkout Statement - Create
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-23 00:15:17 +02:00
|
|
|
def getAmountTaken(data):
|
2018-10-06 12:35:49 +02:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
|
2016-08-23 00:15:17 +02:00
|
|
|
|
|
|
|
def getAmountBalance(data):
|
2018-10-06 12:35:49 +02:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2016-08-23 00:15:17 +02:00
|
|
|
|
2019-11-21 01:18:38 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-11 15:14:23 +02:00
|
|
|
class CheckoutStatementCreate(SuccessMessageMixin, CreateView):
|
2018-10-06 12:35:49 +02:00
|
|
|
model = CheckoutStatement
|
|
|
|
template_name = "kfet/checkoutstatement_create.html"
|
|
|
|
form_class = CheckoutStatementCreateForm
|
|
|
|
success_message = "Nouveau relevé : %(checkout)s - %(at)s"
|
2016-08-11 15:14:23 +02:00
|
|
|
|
|
|
|
def get_success_url(self):
|
2018-10-06 12:35:49 +02:00
|
|
|
return reverse_lazy(
|
|
|
|
"kfet.checkout.read", kwargs={"pk": self.kwargs["pk_checkout"]}
|
|
|
|
)
|
2016-08-11 15:14:23 +02:00
|
|
|
|
|
|
|
def get_success_message(self, cleaned_data):
|
|
|
|
return self.success_message % dict(
|
2018-10-06 12:35:49 +02:00
|
|
|
cleaned_data, checkout=self.object.checkout.name, at=self.object.at
|
|
|
|
)
|
2016-08-11 15:14:23 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
checkout = Checkout.objects.get(pk=self.kwargs["pk_checkout"])
|
|
|
|
context["checkout"] = checkout
|
2016-08-11 15:14:23 +02:00
|
|
|
return context
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.add_checkoutstatement"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-22 01:57:28 +02:00
|
|
|
return self.form_invalid(form)
|
2016-08-11 15:14:23 +02:00
|
|
|
# Creating
|
2016-08-23 00:15:17 +02:00
|
|
|
form.instance.amount_taken = getAmountTaken(form.instance)
|
2016-08-23 02:45:49 +02:00
|
|
|
if not form.instance.not_count:
|
|
|
|
form.instance.balance_new = getAmountBalance(form.cleaned_data)
|
2018-10-06 12:35:49 +02:00
|
|
|
form.instance.checkout_id = self.kwargs["pk_checkout"]
|
2016-08-11 15:14:23 +02:00
|
|
|
form.instance.by = self.request.user.profile.account_kfet
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-11 15:14:23 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2019-11-21 01:18:38 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-23 00:15:17 +02:00
|
|
|
class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView):
|
|
|
|
model = CheckoutStatement
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/checkoutstatement_update.html"
|
2016-08-23 00:15:17 +02:00
|
|
|
form_class = CheckoutStatementUpdateForm
|
2018-10-06 12:35:49 +02:00
|
|
|
success_message = "Relevé modifié"
|
2016-08-23 00:15:17 +02:00
|
|
|
|
|
|
|
def get_success_url(self):
|
2018-10-06 12:35:49 +02:00
|
|
|
return reverse_lazy(
|
|
|
|
"kfet.checkout.read", kwargs={"pk": self.kwargs["pk_checkout"]}
|
|
|
|
)
|
2016-08-23 00:15:17 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
checkout = Checkout.objects.get(pk=self.kwargs["pk_checkout"])
|
|
|
|
context["checkout"] = checkout
|
2016-08-23 00:15:17 +02:00
|
|
|
return context
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_checkoutstatement"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-23 00:15:17 +02:00
|
|
|
return self.form_invalid(form)
|
|
|
|
# Updating
|
|
|
|
form.instance.amount_taken = getAmountTaken(form.instance)
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-23 00:15:17 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-04-04 21:36:02 +02:00
|
|
|
# -----
|
|
|
|
# Category views
|
|
|
|
# -----
|
|
|
|
|
|
|
|
|
|
|
|
# Category - General
|
|
|
|
class CategoryList(ListView):
|
2018-10-06 12:35:49 +02:00
|
|
|
queryset = ArticleCategory.objects.prefetch_related("articles").order_by("name")
|
|
|
|
template_name = "kfet/category.html"
|
|
|
|
context_object_name = "categories"
|
2017-04-04 21:36:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
# Category - Update
|
2019-11-21 01:18:38 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2017-04-04 21:36:02 +02:00
|
|
|
class CategoryUpdate(SuccessMessageMixin, UpdateView):
|
|
|
|
model = ArticleCategory
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/category_update.html"
|
2017-04-04 21:36:02 +02:00
|
|
|
form_class = CategoryForm
|
2018-10-06 12:35:49 +02:00
|
|
|
success_url = reverse_lazy("kfet.category")
|
2017-04-04 21:36:02 +02:00
|
|
|
success_message = "Informations mises à jour pour la catégorie : %(name)s"
|
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_articlecategory"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2017-04-04 21:36:02 +02:00
|
|
|
return self.form_invalid(form)
|
|
|
|
|
|
|
|
# Updating
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2017-04-04 21:36:02 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-04 08:23:34 +02:00
|
|
|
# -----
|
|
|
|
# Article views
|
|
|
|
# -----
|
|
|
|
|
|
|
|
|
2017-04-04 21:48:17 +02:00
|
|
|
# Article - General
|
2016-08-04 08:23:34 +02:00
|
|
|
class ArticleList(ListView):
|
2017-04-06 20:30:23 +02:00
|
|
|
queryset = (
|
2018-10-06 12:35:49 +02:00
|
|
|
Article.objects.select_related("category")
|
2017-04-06 20:30:23 +02:00
|
|
|
.prefetch_related(
|
|
|
|
Prefetch(
|
2018-10-06 12:35:49 +02:00
|
|
|
"inventories",
|
|
|
|
queryset=Inventory.objects.order_by("-at"),
|
|
|
|
to_attr="inventory",
|
2017-04-06 20:30:23 +02:00
|
|
|
)
|
|
|
|
)
|
2018-10-06 12:35:49 +02:00
|
|
|
.order_by("category__name", "-is_sold", "name")
|
2017-04-06 20:30:23 +02:00
|
|
|
)
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/article.html"
|
|
|
|
context_object_name = "articles"
|
2018-10-01 15:37:41 +02:00
|
|
|
|
2018-03-22 15:25:03 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
articles = context[self.context_object_name]
|
2018-10-06 12:35:49 +02:00
|
|
|
context["nb_articles"] = len(articles)
|
2018-03-22 15:25:03 +01:00
|
|
|
context[self.context_object_name] = articles.filter(is_sold=True)
|
2018-10-06 12:35:49 +02:00
|
|
|
context["not_sold_articles"] = articles.filter(is_sold=False)
|
2018-03-22 15:25:03 +01:00
|
|
|
return context
|
2016-08-04 08:23:34 +02:00
|
|
|
|
|
|
|
|
2017-04-04 21:48:17 +02:00
|
|
|
# Article - Create
|
2019-12-02 20:41:19 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-04 08:23:34 +02:00
|
|
|
class ArticleCreate(SuccessMessageMixin, CreateView):
|
2017-04-04 21:48:17 +02:00
|
|
|
model = Article
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/article_create.html"
|
2017-04-04 21:48:17 +02:00
|
|
|
form_class = ArticleForm
|
2018-10-06 12:35:49 +02:00
|
|
|
success_message = "Nouvel item : %(category)s - %(name)s"
|
2016-08-04 08:23:34 +02:00
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.add_article"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-21 18:10:35 +02:00
|
|
|
return self.form_invalid(form)
|
|
|
|
|
2016-08-27 00:14:49 +02:00
|
|
|
# Save ici pour save le manytomany suppliers
|
2016-08-26 23:44:57 +02:00
|
|
|
article = form.save()
|
2016-08-27 00:14:49 +02:00
|
|
|
# Save des suppliers déjà existant
|
2018-10-06 12:35:49 +02:00
|
|
|
for supplier in form.cleaned_data["suppliers"]:
|
|
|
|
SupplierArticle.objects.create(article=article, supplier=supplier)
|
2016-08-26 23:44:57 +02:00
|
|
|
|
2016-08-27 00:14:49 +02:00
|
|
|
# Nouveau supplier
|
2018-10-06 12:35:49 +02:00
|
|
|
supplier_new = form.cleaned_data["supplier_new"].strip()
|
2016-08-26 23:44:57 +02:00
|
|
|
if supplier_new:
|
2018-10-06 12:35:49 +02:00
|
|
|
supplier, created = Supplier.objects.get_or_create(name=supplier_new)
|
2016-08-26 23:44:57 +02:00
|
|
|
if created:
|
2018-10-06 12:35:49 +02:00
|
|
|
SupplierArticle.objects.create(article=article, supplier=supplier)
|
2016-08-26 23:44:57 +02:00
|
|
|
|
2016-08-30 17:16:00 +02:00
|
|
|
# Inventaire avec stock initial
|
|
|
|
inventory = Inventory()
|
|
|
|
inventory.by = self.request.user.profile.account_kfet
|
|
|
|
inventory.save()
|
|
|
|
InventoryArticle.objects.create(
|
2018-10-06 12:35:49 +02:00
|
|
|
inventory=inventory,
|
|
|
|
article=article,
|
|
|
|
stock_old=article.stock,
|
|
|
|
stock_new=article.stock,
|
|
|
|
)
|
2016-08-30 17:16:00 +02:00
|
|
|
|
2016-08-04 08:23:34 +02:00
|
|
|
# Creating
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-04 08:23:34 +02:00
|
|
|
|
|
|
|
|
2017-04-04 21:48:17 +02:00
|
|
|
# Article - Read
|
2016-08-04 08:23:34 +02:00
|
|
|
class ArticleRead(DetailView):
|
2017-04-04 21:48:17 +02:00
|
|
|
model = Article
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/article_read.html"
|
|
|
|
context_object_name = "article"
|
2016-08-04 08:23:34 +02:00
|
|
|
|
2016-08-30 18:56:42 +02:00
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
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
|
2016-08-30 18:56:42 +02:00
|
|
|
return context
|
|
|
|
|
2016-08-04 08:23:34 +02:00
|
|
|
|
2017-04-04 21:48:17 +02:00
|
|
|
# Article - Update
|
2019-12-02 20:41:19 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-22 03:57:13 +02:00
|
|
|
class ArticleUpdate(SuccessMessageMixin, UpdateView):
|
2017-04-04 21:48:17 +02:00
|
|
|
model = Article
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/article_update.html"
|
2017-04-04 21:48:17 +02:00
|
|
|
form_class = ArticleRestrictForm
|
2016-08-04 08:23:34 +02:00
|
|
|
success_message = "Informations mises à jour pour l'article : %(name)s"
|
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_article"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-21 18:10:35 +02:00
|
|
|
return self.form_invalid(form)
|
2016-08-27 00:14:49 +02:00
|
|
|
|
|
|
|
# Save ici pour save le manytomany suppliers
|
|
|
|
article = form.save()
|
|
|
|
# Save des suppliers déjà existant
|
2018-10-06 12:35:49 +02:00
|
|
|
for supplier in form.cleaned_data["suppliers"]:
|
2016-08-27 00:14:49 +02:00
|
|
|
if supplier not in article.suppliers.all():
|
2018-10-06 12:35:49 +02:00
|
|
|
SupplierArticle.objects.create(article=article, supplier=supplier)
|
2016-08-27 00:14:49 +02:00
|
|
|
|
|
|
|
# On vire les suppliers désélectionnés
|
|
|
|
for supplier in article.suppliers.all():
|
2018-10-06 12:35:49 +02:00
|
|
|
if supplier not in form.cleaned_data["suppliers"]:
|
2016-08-27 00:14:49 +02:00
|
|
|
SupplierArticle.objects.filter(
|
2018-10-06 12:35:49 +02:00
|
|
|
article=article, supplier=supplier
|
|
|
|
).delete()
|
2016-08-27 00:14:49 +02:00
|
|
|
|
|
|
|
# Nouveau supplier
|
2018-10-06 12:35:49 +02:00
|
|
|
supplier_new = form.cleaned_data["supplier_new"].strip()
|
2016-08-27 00:14:49 +02:00
|
|
|
if supplier_new:
|
2018-10-06 12:35:49 +02:00
|
|
|
supplier, created = Supplier.objects.get_or_create(name=supplier_new)
|
2016-08-27 00:14:49 +02:00
|
|
|
if created:
|
2018-10-06 12:35:49 +02:00
|
|
|
SupplierArticle.objects.create(article=article, supplier=supplier)
|
2016-08-27 00:14:49 +02:00
|
|
|
|
2016-08-04 08:23:34 +02:00
|
|
|
# Updating
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-08-06 22:19:52 +02:00
|
|
|
|
2016-12-09 21:45:34 +01:00
|
|
|
|
2019-05-24 19:32:57 +02:00
|
|
|
class ArticleDelete(PermissionRequiredMixin, DeleteView):
|
|
|
|
model = Article
|
|
|
|
success_url = reverse_lazy("kfet.article")
|
|
|
|
success_message = "Article supprimé avec succès !"
|
|
|
|
permission_required = "kfet.delete_article"
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
return redirect("kfet.article.read", self.kwargs.get(self.pk_url_kwarg))
|
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
|
|
messages.success(request, self.success_message)
|
|
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2016-08-06 22:19:52 +02:00
|
|
|
# -----
|
|
|
|
# K-Psul
|
|
|
|
# -----
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-06 22:19:52 +02:00
|
|
|
def kpsul(request):
|
|
|
|
data = {}
|
2018-10-06 12:35:49 +02:00
|
|
|
data["operationgroup_form"] = KPsulOperationGroupForm()
|
|
|
|
data["trigramme_form"] = KPsulAccountForm()
|
|
|
|
data["checkout_form"] = KPsulCheckoutForm()
|
|
|
|
data["operation_formset"] = KPsulOperationFormSet(queryset=Operation.objects.none())
|
|
|
|
return render(request, "kfet/kpsul.html", data)
|
2016-08-06 22:19:52 +02:00
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-22 16:08:21 +02:00
|
|
|
def kpsul_get_settings(request):
|
2017-04-03 20:32:16 +02:00
|
|
|
addcost_for = kfet_config.addcost_for
|
2016-08-22 16:08:21 +02:00
|
|
|
data = {
|
2018-10-06 12:35:49 +02:00
|
|
|
"subvention_cof": kfet_config.subvention_cof,
|
|
|
|
"addcost_for": addcost_for and addcost_for.trigramme or "",
|
|
|
|
"addcost_amount": kfet_config.addcost_amount,
|
2016-08-22 16:08:21 +02:00
|
|
|
}
|
|
|
|
return JsonResponse(data)
|
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-29 14:47:12 +01:00
|
|
|
def account_read_json(request, trigramme):
|
2018-10-06 12:35:49 +02:00
|
|
|
account = get_object_or_404(Account, trigramme=trigramme)
|
2020-03-11 22:30:47 +01:00
|
|
|
if not account.readable:
|
|
|
|
raise Http404
|
2018-10-06 12:35:49 +02:00
|
|
|
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,
|
|
|
|
}
|
2016-08-06 22:19:52 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
2017-11-19 18:41:39 +01:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-06 22:19:52 +02:00
|
|
|
def kpsul_checkout_data(request):
|
2018-10-06 12:35:49 +02:00
|
|
|
pk = request.POST.get("pk", 0)
|
2016-09-05 18:09:34 +02:00
|
|
|
if not pk:
|
|
|
|
pk = 0
|
2017-11-19 18:41:39 +01:00
|
|
|
|
|
|
|
data = (
|
2018-10-06 12:35:49 +02:00
|
|
|
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"),
|
|
|
|
)
|
2016-09-24 18:49:40 +02:00
|
|
|
.select_related(
|
2018-10-06 12:35:49 +02:00
|
|
|
"statements" "statements__by", "statements__by__cofprofile__user"
|
|
|
|
)
|
2016-09-24 18:49:40 +02:00
|
|
|
.filter(pk=pk)
|
2018-10-06 12:35:49 +02:00
|
|
|
.order_by("statements__at")
|
2017-11-19 18:41:39 +01:00
|
|
|
.values(
|
2018-10-06 12:35:49 +02:00
|
|
|
"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",
|
|
|
|
)
|
2017-11-19 18:41:39 +01:00
|
|
|
.last()
|
|
|
|
)
|
2016-09-24 18:49:40 +02:00
|
|
|
if data is None:
|
|
|
|
raise Http404
|
2016-08-06 22:19:52 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-22 05:41:31 +02:00
|
|
|
def kpsul_update_addcost(request):
|
|
|
|
addcost_form = AddcostForm(request.POST)
|
2021-02-28 02:02:31 +01:00
|
|
|
data = {"errors": []}
|
2016-08-22 05:41:31 +02:00
|
|
|
|
|
|
|
if not addcost_form.is_valid():
|
2021-02-28 02:02:31 +01:00
|
|
|
for (field, errors) in addcost_form.errors.items():
|
|
|
|
for error in errors:
|
|
|
|
data["errors"].append({"code": f"invalid_{field}", "message": error})
|
|
|
|
|
2016-08-22 16:29:12 +02:00
|
|
|
return JsonResponse(data, status=400)
|
2021-02-28 02:02:31 +01:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms = ["kfet.manage_addcosts"]
|
2016-08-22 05:41:31 +02:00
|
|
|
if not request.user.has_perms(required_perms):
|
2021-02-28 02:02:31 +01:00
|
|
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
2016-08-22 05:41:31 +02:00
|
|
|
return JsonResponse(data, status=403)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
trigramme = addcost_form.cleaned_data["trigramme"]
|
2016-08-22 05:41:31 +02:00
|
|
|
account = trigramme and Account.objects.get(trigramme=trigramme) or None
|
2018-10-06 12:35:49 +02:00
|
|
|
amount = addcost_form.cleaned_data["amount"]
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
kfet_config.set(addcost_for=account, addcost_amount=amount)
|
2017-04-03 23:06:47 +02:00
|
|
|
|
2022-06-27 15:34:24 +02:00
|
|
|
data = {
|
|
|
|
"addcost": {"for": account and account.trigramme or None, "amount": amount},
|
|
|
|
"type": "kpsul",
|
|
|
|
}
|
|
|
|
|
|
|
|
channel_layer = get_channel_layer()
|
|
|
|
|
|
|
|
async_to_sync(channel_layer.group_send)("kfet.kpsul", data)
|
2016-08-22 05:41:31 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
2017-03-17 19:53:23 +01:00
|
|
|
|
2019-12-21 16:26:59 +01:00
|
|
|
def get_missing_perms(required_perms: List[str], user: User) -> List[str]:
|
2021-02-28 02:02:31 +01:00
|
|
|
def get_perm_name(app_label: str, codename: str) -> str:
|
|
|
|
return Permission.objects.values_list("name", flat=True).get(
|
2019-12-21 16:26:59 +01:00
|
|
|
codename=codename, content_type__app_label=app_label
|
2018-10-06 12:35:49 +02:00
|
|
|
)
|
2019-12-21 16:26:59 +01:00
|
|
|
|
|
|
|
missing_perms = [
|
2021-02-28 02:02:31 +01:00
|
|
|
get_perm_name(*perm.split("."))
|
2019-12-21 16:26:59 +01:00
|
|
|
for perm in required_perms
|
|
|
|
if not user.has_perm(perm)
|
|
|
|
]
|
|
|
|
|
2016-08-09 11:02:26 +02:00
|
|
|
return missing_perms
|
|
|
|
|
2017-03-17 19:53:23 +01:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-06 22:19:52 +02:00
|
|
|
def kpsul_perform_operations(request):
|
2016-08-07 17:02:01 +02:00
|
|
|
# Initializing response data
|
2021-02-28 02:02:31 +01:00
|
|
|
data = {"errors": []}
|
2016-08-06 22:19:52 +02:00
|
|
|
|
2016-08-07 17:02:01 +02:00
|
|
|
# Checking operationgroup
|
|
|
|
operationgroup_form = KPsulOperationGroupForm(request.POST)
|
|
|
|
if not operationgroup_form.is_valid():
|
2021-02-28 02:02:31 +01:00
|
|
|
for field in operationgroup_form.errors:
|
|
|
|
verbose_field, feminin = (
|
|
|
|
("compte", "") if field == "on_acc" else ("caisse", "e")
|
|
|
|
)
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": f"invalid_{field}",
|
|
|
|
"message": f"Pas de {verbose_field} sélectionné{feminin}",
|
|
|
|
}
|
|
|
|
)
|
2016-08-07 17:02:01 +02:00
|
|
|
|
|
|
|
# Checking operation_formset
|
2017-03-17 19:53:23 +01:00
|
|
|
operation_formset = KPsulOperationFormSet(request.POST)
|
2016-08-07 17:02:01 +02:00
|
|
|
if not operation_formset.is_valid():
|
2021-02-28 02:02:31 +01:00
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "invalid_formset",
|
|
|
|
"message": "Formulaire d'opérations vide ou invalide",
|
|
|
|
}
|
|
|
|
)
|
2016-08-07 17:02:01 +02:00
|
|
|
|
2016-08-07 23:41:46 +02:00
|
|
|
# Returning BAD REQUEST if errors
|
2018-10-06 12:35:49 +02:00
|
|
|
if data["errors"]:
|
2016-08-06 22:19:52 +02:00
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
2016-08-07 17:22:39 +02:00
|
|
|
# Pre-saving (no commit)
|
2017-03-17 19:53:23 +01:00
|
|
|
operationgroup = operationgroup_form.save(commit=False)
|
|
|
|
operations = operation_formset.save(commit=False)
|
2021-02-28 02:02:31 +01:00
|
|
|
on_acc = operationgroup.on_acc
|
2016-08-07 17:02:01 +02:00
|
|
|
|
2016-08-07 18:37:06 +02:00
|
|
|
# Retrieving COF grant
|
2017-04-03 20:32:16 +02:00
|
|
|
cof_grant = kfet_config.subvention_cof
|
2016-08-08 02:50:04 +02:00
|
|
|
# Retrieving addcosts data
|
2017-04-03 20:32:16 +02:00
|
|
|
addcost_amount = kfet_config.addcost_amount
|
|
|
|
addcost_for = kfet_config.addcost_for
|
2016-08-07 18:37:06 +02:00
|
|
|
|
2016-08-08 02:50:04 +02:00
|
|
|
# Initializing vars
|
2017-03-17 19:53:23 +01:00
|
|
|
required_perms = set() # Required perms to perform all operations
|
2016-08-08 02:50:04 +02:00
|
|
|
cof_grant_divisor = 1 + cof_grant / 100
|
2017-03-17 19:53:23 +01:00
|
|
|
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
|
2021-02-28 02:02:31 +01:00
|
|
|
is_addcost = all((addcost_for, addcost_amount, addcost_for != on_acc))
|
|
|
|
need_comment = on_acc.need_comment
|
2016-08-11 06:13:31 +02:00
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if on_acc.is_frozen:
|
|
|
|
data["errors"].append(
|
|
|
|
{"code": "frozen_acc", "message": f"Le compte {on_acc.trigramme} est gelé"}
|
|
|
|
)
|
2021-02-24 00:25:48 +01:00
|
|
|
|
2017-03-17 19:53:23 +01:00
|
|
|
# Filling data of each operations
|
|
|
|
# + operationgroup + calculating other stuffs
|
2016-08-06 22:19:52 +02:00
|
|
|
for operation in operations:
|
2016-08-07 17:22:39 +02:00
|
|
|
if operation.type == Operation.PURCHASE:
|
2018-10-06 12:35:49 +02:00
|
|
|
operation.amount = -operation.article.price * operation.article_nb
|
2017-03-10 18:28:48 +01:00
|
|
|
if is_addcost & operation.article.category.has_addcost:
|
2017-03-17 19:53:23 +01:00
|
|
|
operation.addcost_for = addcost_for
|
2018-10-06 12:35:49 +02:00
|
|
|
operation.addcost_amount = addcost_amount * operation.article_nb
|
2017-03-17 19:53:23 +01:00
|
|
|
operation.amount -= operation.addcost_amount
|
|
|
|
to_addcost_for_balance += operation.addcost_amount
|
2021-02-28 02:02:31 +01:00
|
|
|
if on_acc.is_cash:
|
2016-08-08 12:46:43 +02:00
|
|
|
to_checkout_balance += -operation.amount
|
2021-02-28 02:02:31 +01:00
|
|
|
if on_acc.is_cof and operation.article.category.has_reduction:
|
2017-04-05 14:57:26 +02:00
|
|
|
if is_addcost and operation.article.category.has_addcost:
|
2017-04-04 16:57:17 +02:00
|
|
|
operation.addcost_amount /= cof_grant_divisor
|
2017-01-27 13:08:50 +01:00
|
|
|
operation.amount = operation.amount / cof_grant_divisor
|
2016-08-11 06:13:31 +02:00
|
|
|
to_articles_stocks[operation.article] -= operation.article_nb
|
2016-08-08 12:46:43 +02:00
|
|
|
else:
|
2021-02-28 02:02:31 +01:00
|
|
|
if on_acc.is_cash:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "invalid_liq",
|
|
|
|
"message": (
|
|
|
|
"Impossible de compter autre chose que des achats sur LIQ"
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
2017-03-25 00:52:49 +01:00
|
|
|
if operation.type != Operation.EDIT:
|
|
|
|
to_checkout_balance += operation.amount
|
2016-08-08 00:41:31 +02:00
|
|
|
operationgroup.amount += operation.amount
|
2016-08-07 23:41:46 +02:00
|
|
|
if operation.type == Operation.DEPOSIT:
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms.add("kfet.perform_deposit")
|
2017-03-25 00:52:49 +01:00
|
|
|
if operation.type == Operation.EDIT:
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms.add("kfet.edit_balance_account")
|
2016-08-31 01:36:58 +02:00
|
|
|
need_comment = True
|
2021-02-28 02:16:40 +01:00
|
|
|
if on_acc.is_cof:
|
2016-08-22 18:08:44 +02:00
|
|
|
to_addcost_for_balance = to_addcost_for_balance / cof_grant_divisor
|
2016-08-07 23:41:46 +02:00
|
|
|
|
2021-02-28 02:16:40 +01:00
|
|
|
(perms, stop) = on_acc.perms_to_perform_operation(amount=operationgroup.amount)
|
2016-08-11 06:13:31 +02:00
|
|
|
required_perms |= perms
|
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if stop:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "negative",
|
2021-02-28 02:16:40 +01:00
|
|
|
"message": f"Le compte {on_acc.trigramme} a un solde insuffisant.",
|
2021-02-28 02:02:31 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2016-08-31 01:36:58 +02:00
|
|
|
if need_comment:
|
2016-08-23 15:43:16 +02:00
|
|
|
operationgroup.comment = operationgroup.comment.strip()
|
|
|
|
if not operationgroup.comment:
|
2021-02-28 02:02:31 +01:00
|
|
|
data["need_comment"] = True
|
2017-03-31 23:28:03 +02:00
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if data["errors"] or "need_comment" in data:
|
2017-03-31 23:28:03 +02:00
|
|
|
return JsonResponse(data, status=400)
|
2016-08-23 15:43:16 +02:00
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if not request.user.has_perms(required_perms):
|
|
|
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
2016-08-11 06:13:31 +02:00
|
|
|
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
|
2021-02-28 02:02:31 +01:00
|
|
|
operationgroup.is_cof = on_acc.is_cof
|
2016-08-11 06:13:31 +02:00
|
|
|
|
2016-08-08 03:32:48 +02:00
|
|
|
# Starting transaction to ensure data consistency
|
2016-08-11 06:13:31 +02:00
|
|
|
with transaction.atomic():
|
|
|
|
# If not cash account,
|
|
|
|
# saving account's balance and adding to Negative if not in
|
2017-04-13 15:15:59 +02:00
|
|
|
if not on_acc.is_cash:
|
|
|
|
(
|
2018-10-06 12:35:49 +02:00
|
|
|
Account.objects.filter(pk=on_acc.pk).update(
|
|
|
|
balance=F("balance") + operationgroup.amount
|
|
|
|
)
|
2017-04-13 15:15:59 +02:00
|
|
|
)
|
|
|
|
on_acc.refresh_from_db()
|
2017-04-13 15:48:13 +02:00
|
|
|
on_acc.update_negative()
|
2016-08-08 12:46:43 +02:00
|
|
|
|
2016-08-11 06:13:31 +02:00
|
|
|
# Updating checkout's balance
|
|
|
|
if to_checkout_balance:
|
|
|
|
Checkout.objects.filter(pk=operationgroup.checkout.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
balance=F("balance") + to_checkout_balance
|
|
|
|
)
|
2016-08-08 07:44:05 +02:00
|
|
|
|
2016-08-11 06:13:31 +02:00
|
|
|
# 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(
|
2018-10-06 12:35:49 +02:00
|
|
|
balance=F("balance") + to_addcost_for_balance
|
|
|
|
)
|
2016-08-11 06:13:31 +02:00
|
|
|
|
|
|
|
# Saving operation group
|
|
|
|
operationgroup.save()
|
|
|
|
# Filling operationgroup id for each operations and saving
|
|
|
|
for operation in operations:
|
|
|
|
operation.group = operationgroup
|
|
|
|
operation.save()
|
|
|
|
|
|
|
|
# Updating articles stock
|
|
|
|
for article in to_articles_stocks:
|
|
|
|
Article.objects.filter(pk=article.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
stock=F("stock") + to_articles_stocks[article]
|
|
|
|
)
|
2016-08-06 22:19:52 +02:00
|
|
|
|
2016-08-14 19:59:36 +02:00
|
|
|
# Websocket data
|
2022-06-27 15:34:24 +02:00
|
|
|
websocket_data = {"type": "kpsul"}
|
2019-12-25 17:28:36 +01:00
|
|
|
websocket_data["groups"] = [
|
2018-10-06 12:35:49 +02:00
|
|
|
{
|
|
|
|
"add": True,
|
2019-12-25 11:34:34 +01:00
|
|
|
"type": "operation",
|
2018-10-06 12:35:49 +02:00
|
|
|
"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
|
|
|
|
),
|
2021-02-28 02:02:31 +01:00
|
|
|
"on_acc__trigramme": on_acc.trigramme,
|
2019-12-25 17:28:36 +01:00
|
|
|
"entries": [],
|
2018-10-06 12:35:49 +02:00
|
|
|
}
|
|
|
|
]
|
2016-08-14 19:59:36 +02:00
|
|
|
for operation in operations:
|
|
|
|
ope_data = {
|
2018-10-06 12:35:49 +02:00
|
|
|
"id": operation.pk,
|
|
|
|
"type": operation.type,
|
|
|
|
"amount": operation.amount,
|
|
|
|
"addcost_amount": operation.addcost_amount,
|
|
|
|
"addcost_for__trigramme": (
|
|
|
|
operation.addcost_for and addcost_for.trigramme or None
|
|
|
|
),
|
|
|
|
"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,
|
2016-08-14 19:59:36 +02:00
|
|
|
}
|
2019-12-25 17:28:36 +01:00
|
|
|
websocket_data["groups"][0]["entries"].append(ope_data)
|
2016-08-14 23:37:05 +02:00
|
|
|
# Need refresh from db cause we used update on queryset
|
2016-08-14 19:59:36 +02:00
|
|
|
operationgroup.checkout.refresh_from_db()
|
2018-10-06 12:35:49 +02:00
|
|
|
websocket_data["checkouts"] = [
|
|
|
|
{"id": operationgroup.checkout.pk, "balance": operationgroup.checkout.balance}
|
|
|
|
]
|
|
|
|
websocket_data["articles"] = []
|
2016-08-14 23:37:05 +02:00
|
|
|
# Need refresh from db cause we used update on querysets
|
2017-03-17 19:53:23 +01:00
|
|
|
articles_pk = [article.pk for article in to_articles_stocks]
|
2018-10-06 12:35:49 +02:00
|
|
|
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
|
2016-08-14 23:37:05 +02:00
|
|
|
for article in articles:
|
2018-10-06 12:35:49 +02:00
|
|
|
websocket_data["articles"].append(
|
|
|
|
{"id": article["id"], "stock": article["stock"]}
|
|
|
|
)
|
2022-06-27 15:34:24 +02:00
|
|
|
|
|
|
|
channel_layer = get_channel_layer()
|
|
|
|
|
|
|
|
async_to_sync(channel_layer.group_send)("kfet.kpsul", websocket_data)
|
2016-08-06 22:19:52 +02:00
|
|
|
return JsonResponse(data)
|
2016-08-09 11:02:26 +02:00
|
|
|
|
2017-03-17 19:53:23 +01:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2019-12-23 14:16:23 +01:00
|
|
|
def cancel_operations(request):
|
2016-08-09 11:02:26 +02:00
|
|
|
# Pour la réponse
|
2021-02-28 02:02:31 +01:00
|
|
|
data = {"canceled": [], "warnings": {}, "errors": []}
|
2016-08-09 11:02:26 +02:00
|
|
|
|
|
|
|
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
|
|
|
try:
|
|
|
|
# Set pour virer les doublons
|
2018-10-06 12:35:49 +02:00
|
|
|
opes_post = set(
|
|
|
|
map(int, filter(None, request.POST.getlist("operations[]", [])))
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
except ValueError:
|
2021-02-28 02:02:31 +01:00
|
|
|
data["errors"].append(
|
|
|
|
{"code": "invalid_request", "message": "Requête invalide !"}
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
return JsonResponse(data, status=400)
|
2021-02-28 02:02:31 +01:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
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]
|
2016-08-09 11:02:26 +02:00
|
|
|
if opes_notexisting:
|
2021-02-28 02:02:31 +01:00
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "cancel_missing",
|
|
|
|
"message": "Opérations inexistantes : {}".format(
|
|
|
|
", ".join(map(str, opes_notexisting))
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
opes_already_canceled = [] # Déjà annulée
|
|
|
|
opes = [] # Pas déjà annulée
|
2016-08-09 11:02:26 +02:00
|
|
|
required_perms = set()
|
2017-04-03 20:32:16 +02:00
|
|
|
cancel_duration = kfet_config.cancel_duration
|
2021-02-28 02:02:31 +01:00
|
|
|
|
|
|
|
# Modifs à faire sur les balances des comptes
|
|
|
|
to_accounts_balances = defaultdict(int)
|
|
|
|
# ------ sur les montants des groupes d'opé
|
|
|
|
to_groups_amounts = defaultdict(int)
|
|
|
|
# ------ sur les balances de caisses
|
|
|
|
to_checkouts_balances = defaultdict(int)
|
|
|
|
# ------ sur les stocks d'articles
|
|
|
|
to_articles_stocks = defaultdict(int)
|
|
|
|
|
2016-08-09 11:02:26 +02:00
|
|
|
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():
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms.add("kfet.cancel_old_operations")
|
2016-08-09 11:02:26 +02:00
|
|
|
|
|
|
|
# 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
|
2016-08-23 03:27:02 +02:00
|
|
|
|
2016-08-09 11:02:26 +02:00
|
|
|
# Pour les balances de caisses
|
2016-08-23 03:27:02 +02:00
|
|
|
# Les balances de caisses dont il y a eu un relevé depuis la date
|
|
|
|
# de la commande ne doivent pas être modifiées
|
2016-08-27 21:43:19 +02:00
|
|
|
# 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
|
2016-08-23 03:27:02 +02:00
|
|
|
# TODO ? : Maj les balance_old de relevés pour modifier l'erreur
|
2018-10-06 12:35:49 +02:00
|
|
|
last_statement = (
|
|
|
|
CheckoutStatement.objects.filter(checkout=ope.group.checkout)
|
|
|
|
.order_by("at")
|
|
|
|
.last()
|
|
|
|
)
|
2016-08-23 03:27:02 +02:00
|
|
|
if not last_statement or last_statement.at < ope.group.at:
|
2017-03-25 00:52:49 +01:00
|
|
|
if ope.is_checkout:
|
2016-08-23 03:27:02 +02:00
|
|
|
if ope.group.on_acc.is_cash:
|
2018-10-06 12:35:49 +02:00
|
|
|
to_checkouts_balances[ope.group.checkout] -= -ope.amount
|
2017-03-25 00:52:49 +01:00
|
|
|
else:
|
|
|
|
to_checkouts_balances[ope.group.checkout] -= ope.amount
|
2016-08-23 03:27:02 +02:00
|
|
|
|
2016-08-09 11:02:26 +02:00
|
|
|
# Pour les stocks d'articles
|
2016-08-27 21:43:19 +02:00
|
|
|
# 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
|
2016-08-09 11:02:26 +02:00
|
|
|
if ope.article and ope.article_nb:
|
2018-10-06 12:35:49 +02:00
|
|
|
last_stock = (
|
|
|
|
InventoryArticle.objects.select_related("inventory")
|
2016-08-27 21:43:19 +02:00
|
|
|
.filter(article=ope.article)
|
2018-10-06 12:35:49 +02:00
|
|
|
.order_by("inventory__at")
|
|
|
|
.last()
|
|
|
|
)
|
2016-08-27 21:43:19 +02:00
|
|
|
if not last_stock or last_stock.inventory.at < ope.group.at:
|
|
|
|
to_articles_stocks[ope.article] += ope.article_nb
|
2016-08-09 11:02:26 +02:00
|
|
|
|
|
|
|
if not opes:
|
2018-10-06 12:35:49 +02:00
|
|
|
data["warnings"]["already_canceled"] = opes_already_canceled
|
2016-08-09 11:02:26 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
2016-09-03 18:32:12 +02:00
|
|
|
negative_accounts = []
|
2016-08-09 11:02:26 +02:00
|
|
|
# Checking permissions or stop
|
|
|
|
for account in to_accounts_balances:
|
|
|
|
(perms, stop) = account.perms_to_perform_operation(
|
2018-10-06 12:35:49 +02:00
|
|
|
amount=to_accounts_balances[account]
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
required_perms |= perms
|
2016-09-03 18:32:12 +02:00
|
|
|
if stop:
|
|
|
|
negative_accounts.append(account.trigramme)
|
2016-08-09 11:02:26 +02:00
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if negative_accounts:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "negative",
|
|
|
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
|
|
|
", ".join(negative_accounts)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
|
|
|
if not request.user.has_perms(required_perms):
|
|
|
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
2016-08-09 11:02:26 +02:00
|
|
|
return JsonResponse(data, status=403)
|
|
|
|
|
2016-08-14 19:59:36 +02:00
|
|
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
|
|
|
canceled_at = timezone.now()
|
|
|
|
|
2016-08-09 11:02:26 +02:00
|
|
|
with transaction.atomic():
|
2018-10-06 12:35:49 +02:00
|
|
|
(
|
|
|
|
Operation.objects.filter(pk__in=opes).update(
|
|
|
|
canceled_by=canceled_by, canceled_at=canceled_at
|
|
|
|
)
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
for account in to_accounts_balances:
|
2017-04-13 15:15:59 +02:00
|
|
|
(
|
2018-10-06 12:35:49 +02:00
|
|
|
Account.objects.filter(pk=account.pk).update(
|
|
|
|
balance=F("balance") + to_accounts_balances[account]
|
|
|
|
)
|
2017-04-13 15:15:59 +02:00
|
|
|
)
|
|
|
|
if not account.is_cash:
|
|
|
|
# Should always be true, but we want to be sure
|
|
|
|
account.refresh_from_db()
|
2017-04-13 15:48:13 +02:00
|
|
|
account.update_negative()
|
2016-08-09 11:02:26 +02:00
|
|
|
for checkout in to_checkouts_balances:
|
|
|
|
Checkout.objects.filter(pk=checkout.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
balance=F("balance") + to_checkouts_balances[checkout]
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
for group in to_groups_amounts:
|
|
|
|
OperationGroup.objects.filter(pk=group.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
amount=F("amount") + to_groups_amounts[group]
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
for article in to_articles_stocks:
|
|
|
|
Article.objects.filter(pk=article.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
stock=F("stock") + to_articles_stocks[article]
|
|
|
|
)
|
2016-08-09 11:02:26 +02:00
|
|
|
|
2019-01-06 10:26:00 +01:00
|
|
|
# Need refresh from db cause we used update on querysets.
|
|
|
|
# Sort objects by pk to get deterministic responses.
|
2018-10-06 12:35:49 +02:00
|
|
|
opegroups_pk = [opegroup.pk for opegroup in to_groups_amounts]
|
2019-01-06 10:26:00 +01:00
|
|
|
opegroups = (
|
|
|
|
OperationGroup.objects.values("id", "amount", "is_cof")
|
|
|
|
.filter(pk__in=opegroups_pk)
|
|
|
|
.order_by("pk")
|
|
|
|
)
|
2019-12-23 15:09:41 +01:00
|
|
|
opes = (
|
|
|
|
Operation.objects.values("id", "canceled_at", "canceled_by__trigramme")
|
|
|
|
.filter(pk__in=opes)
|
|
|
|
.order_by("pk")
|
|
|
|
)
|
2019-01-06 10:26:00 +01:00
|
|
|
checkouts_pk = [checkout.pk for checkout in to_checkouts_balances]
|
|
|
|
checkouts = (
|
|
|
|
Checkout.objects.values("id", "balance")
|
|
|
|
.filter(pk__in=checkouts_pk)
|
|
|
|
.order_by("pk")
|
2018-10-06 12:35:49 +02:00
|
|
|
)
|
2019-01-06 10:26:00 +01:00
|
|
|
articles_pk = [article.pk for articles in to_articles_stocks]
|
|
|
|
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
|
|
|
|
|
|
|
|
# Websocket data
|
2022-06-27 15:34:24 +02:00
|
|
|
websocket_data = {"checkouts": [], "articles": [], "type": "kpsul"}
|
2016-08-14 23:37:05 +02:00
|
|
|
for checkout in checkouts:
|
2018-10-06 12:35:49 +02:00
|
|
|
websocket_data["checkouts"].append(
|
|
|
|
{"id": checkout["id"], "balance": checkout["balance"]}
|
|
|
|
)
|
2016-08-14 23:37:05 +02:00
|
|
|
for article in articles:
|
2018-10-06 12:35:49 +02:00
|
|
|
websocket_data["articles"].append(
|
|
|
|
{"id": article["id"], "stock": article["stock"]}
|
|
|
|
)
|
2022-06-27 15:34:24 +02:00
|
|
|
|
|
|
|
channel_layer = get_channel_layer()
|
|
|
|
|
|
|
|
async_to_sync(channel_layer.group_send)("kfet.kpsul", websocket_data)
|
2016-08-14 19:59:36 +02:00
|
|
|
|
2019-12-23 15:09:41 +01:00
|
|
|
data["canceled"] = list(opes)
|
|
|
|
data["opegroups_to_update"] = list(opegroups)
|
2016-08-09 11:02:26 +02:00
|
|
|
if opes_already_canceled:
|
2018-10-06 12:35:49 +02:00
|
|
|
data["warnings"]["already_canceled"] = opes_already_canceled
|
2016-08-09 11:02:26 +02:00
|
|
|
return JsonResponse(data)
|
2016-08-14 19:59:36 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2022-05-20 12:08:59 +02:00
|
|
|
def get_history_limit(user) -> Tuple[datetime, datetime]:
|
|
|
|
"""returns a tuple of 2 dates
|
|
|
|
- the earliest date the given user can view history of any account
|
|
|
|
- the earliest date the given user can view history of special accounts
|
|
|
|
(LIQ and #13)"""
|
2021-02-19 12:13:23 +01:00
|
|
|
now = timezone.now()
|
2021-02-19 11:51:48 +01:00
|
|
|
if user.has_perm("kfet.access_old_history"):
|
2022-05-20 12:08:59 +02:00
|
|
|
return (
|
|
|
|
now - settings.KFET_HISTORY_LONG_DATE_LIMIT,
|
|
|
|
settings.KFET_HISTORY_NO_DATE_LIMIT,
|
|
|
|
)
|
2021-02-19 11:51:48 +01:00
|
|
|
if user.has_perm("kfet.is_team"):
|
2022-05-20 12:08:59 +02:00
|
|
|
limit = now - settings.KFET_HISTORY_DATE_LIMIT
|
|
|
|
return limit, limit
|
2021-02-19 11:51:48 +01:00
|
|
|
# should not happen - future earliest date
|
2022-05-20 12:08:59 +02:00
|
|
|
future = now + timedelta(days=1)
|
|
|
|
return future, future
|
2021-02-19 10:48:24 +01:00
|
|
|
|
|
|
|
|
2016-08-24 23:34:14 +02:00
|
|
|
@login_required
|
2016-08-24 02:05:05 +02:00
|
|
|
def history_json(request):
|
|
|
|
# Récupération des paramètres
|
2020-09-15 20:03:37 +02:00
|
|
|
form = FilterHistoryForm(request.GET)
|
|
|
|
|
|
|
|
if not form.is_valid():
|
|
|
|
return HttpResponseBadRequest()
|
|
|
|
|
|
|
|
start = form.cleaned_data["start"]
|
|
|
|
end = form.cleaned_data["end"]
|
|
|
|
account = form.cleaned_data["account"]
|
|
|
|
checkout = form.cleaned_data["checkout"]
|
|
|
|
transfers_only = form.cleaned_data["transfers_only"]
|
|
|
|
opes_only = form.cleaned_data["opes_only"]
|
2016-08-24 02:05:05 +02:00
|
|
|
|
2019-12-25 12:39:41 +01:00
|
|
|
# Construction de la requête (sur les transferts) pour le prefetch
|
2019-12-23 11:06:48 +01:00
|
|
|
|
|
|
|
transfer_queryset_prefetch = Transfer.objects.select_related(
|
|
|
|
"from_acc", "to_acc", "canceled_by"
|
|
|
|
)
|
|
|
|
|
2019-12-25 12:39:41 +01:00
|
|
|
# Le check sur les comptes est dans le prefetch pour les transferts
|
2020-09-15 20:03:37 +02:00
|
|
|
if account:
|
2019-12-23 11:06:48 +01:00
|
|
|
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
|
2020-09-15 20:03:37 +02:00
|
|
|
Q(from_acc=account) | Q(to_acc=account)
|
2019-12-23 11:06:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if not request.user.has_perm("kfet.is_team"):
|
2019-12-25 12:35:46 +01:00
|
|
|
try:
|
|
|
|
acc = request.user.profile.account_kfet
|
|
|
|
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
|
|
|
|
Q(from_acc=acc) | Q(to_acc=acc)
|
|
|
|
)
|
|
|
|
except Account.DoesNotExist:
|
|
|
|
return JsonResponse({}, status=403)
|
2019-12-23 11:06:48 +01:00
|
|
|
|
|
|
|
transfer_prefetch = Prefetch(
|
|
|
|
"transfers", queryset=transfer_queryset_prefetch, to_attr="filtered_transfers"
|
|
|
|
)
|
2016-08-24 02:05:05 +02:00
|
|
|
|
2019-12-25 12:39:41 +01:00
|
|
|
# Construction de la requête (sur les opérations) pour le prefetch
|
|
|
|
ope_queryset_prefetch = Operation.objects.select_related(
|
|
|
|
"article", "canceled_by", "addcost_for"
|
|
|
|
)
|
|
|
|
ope_prefetch = Prefetch("opes", queryset=ope_queryset_prefetch)
|
|
|
|
|
2016-08-24 02:05:05 +02:00
|
|
|
# Construction de la requête principale
|
2017-11-19 18:41:39 +01:00
|
|
|
opegroups = (
|
2019-12-23 11:06:48 +01:00
|
|
|
OperationGroup.objects.prefetch_related(ope_prefetch)
|
2018-10-06 12:35:49 +02:00
|
|
|
.select_related("on_acc", "valid_by")
|
|
|
|
.order_by("at")
|
2016-08-24 02:05:05 +02:00
|
|
|
)
|
2019-12-23 11:06:48 +01:00
|
|
|
transfergroups = (
|
|
|
|
TransferGroup.objects.prefetch_related(transfer_prefetch)
|
|
|
|
.select_related("valid_by")
|
|
|
|
.order_by("at")
|
|
|
|
)
|
|
|
|
|
2021-02-10 21:32:44 +01:00
|
|
|
# limite l'accès à l'historique plus vieux que settings.KFET_HISTORY_DATE_LIMIT
|
|
|
|
limit_date = True
|
|
|
|
|
2016-08-24 02:05:05 +02:00
|
|
|
# Application des filtres
|
2020-09-15 20:03:37 +02:00
|
|
|
if start:
|
|
|
|
opegroups = opegroups.filter(at__gte=start)
|
|
|
|
transfergroups = transfergroups.filter(at__gte=start)
|
|
|
|
if end:
|
|
|
|
opegroups = opegroups.filter(at__lt=end)
|
|
|
|
transfergroups = transfergroups.filter(at__lt=end)
|
|
|
|
if checkout:
|
2021-02-06 18:58:25 +01:00
|
|
|
opegroups = opegroups.filter(checkout=checkout)
|
2019-12-23 11:06:48 +01:00
|
|
|
transfergroups = TransferGroup.objects.none()
|
|
|
|
if transfers_only:
|
|
|
|
opegroups = OperationGroup.objects.none()
|
|
|
|
if opes_only:
|
|
|
|
transfergroups = TransferGroup.objects.none()
|
2020-09-15 20:03:37 +02:00
|
|
|
if account:
|
|
|
|
opegroups = opegroups.filter(on_acc=account)
|
2021-02-19 12:16:43 +01:00
|
|
|
if account.user == request.user:
|
2021-02-10 21:32:44 +01:00
|
|
|
limit_date = False # pas de limite de date sur son propre historique
|
2016-08-24 23:34:14 +02:00
|
|
|
# Un non-membre de l'équipe n'a que accès à son historique
|
2021-02-19 10:18:47 +01:00
|
|
|
elif not request.user.has_perm("kfet.is_team"):
|
|
|
|
# un non membre de la kfet doit avoir le champ account
|
|
|
|
# pré-rempli, cette requête est douteuse
|
|
|
|
return JsonResponse({}, status=403)
|
2021-02-10 21:32:44 +01:00
|
|
|
if limit_date:
|
|
|
|
# limiter l'accès à l'historique ancien pour confidentialité
|
2022-05-20 12:08:59 +02:00
|
|
|
earliest_date, earliest_date_no_limit = get_history_limit(request.user)
|
|
|
|
if (
|
|
|
|
account
|
|
|
|
and account.trigramme in settings.KFET_HISTORY_NO_DATE_LIMIT_TRIGRAMMES
|
|
|
|
):
|
|
|
|
earliest_date = earliest_date_no_limit
|
2021-02-10 21:32:44 +01:00
|
|
|
opegroups = opegroups.filter(at__gte=earliest_date)
|
|
|
|
transfergroups = transfergroups.filter(at__gte=earliest_date)
|
2016-09-05 14:39:31 +02:00
|
|
|
|
2016-08-24 02:05:05 +02:00
|
|
|
# Construction de la réponse
|
2019-12-25 17:28:36 +01:00
|
|
|
history_groups = []
|
2016-08-14 19:59:36 +02:00
|
|
|
for opegroup in opegroups:
|
2016-08-24 02:05:05 +02:00
|
|
|
opegroup_dict = {
|
2019-12-23 15:09:41 +01:00
|
|
|
"type": "operation",
|
2018-10-06 12:35:49 +02:00
|
|
|
"id": opegroup.id,
|
|
|
|
"amount": opegroup.amount,
|
|
|
|
"at": opegroup.at,
|
|
|
|
"checkout_id": opegroup.checkout_id,
|
|
|
|
"is_cof": opegroup.is_cof,
|
|
|
|
"comment": opegroup.comment,
|
2019-12-25 17:28:36 +01:00
|
|
|
"entries": [],
|
2018-10-06 12:35:49 +02:00
|
|
|
"on_acc__trigramme": opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
2016-08-24 02:05:05 +02:00
|
|
|
}
|
2018-10-06 12:35:49 +02:00
|
|
|
if request.user.has_perm("kfet.is_team"):
|
|
|
|
opegroup_dict["valid_by__trigramme"] = (
|
|
|
|
opegroup.valid_by and opegroup.valid_by.trigramme or None
|
|
|
|
)
|
2016-08-24 02:05:05 +02:00
|
|
|
for ope in opegroup.opes.all():
|
|
|
|
ope_dict = {
|
2018-10-06 12:35:49 +02:00
|
|
|
"id": ope.id,
|
|
|
|
"type": ope.type,
|
|
|
|
"amount": ope.amount,
|
|
|
|
"article_nb": ope.article_nb,
|
|
|
|
"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,
|
2016-08-24 02:05:05 +02:00
|
|
|
}
|
2018-10-06 12:35:49 +02:00
|
|
|
if request.user.has_perm("kfet.is_team"):
|
|
|
|
ope_dict["canceled_by__trigramme"] = (
|
|
|
|
ope.canceled_by and ope.canceled_by.trigramme or None
|
|
|
|
)
|
2019-12-25 17:28:36 +01:00
|
|
|
opegroup_dict["entries"].append(ope_dict)
|
|
|
|
history_groups.append(opegroup_dict)
|
2019-12-23 11:07:02 +01:00
|
|
|
for transfergroup in transfergroups:
|
|
|
|
if transfergroup.filtered_transfers:
|
|
|
|
transfergroup_dict = {
|
2019-12-23 15:09:41 +01:00
|
|
|
"type": "transfer",
|
2019-12-23 11:07:02 +01:00
|
|
|
"id": transfergroup.id,
|
|
|
|
"at": transfergroup.at,
|
|
|
|
"comment": transfergroup.comment,
|
2019-12-25 17:28:36 +01:00
|
|
|
"entries": [],
|
2019-12-23 11:07:02 +01:00
|
|
|
}
|
|
|
|
if request.user.has_perm("kfet.is_team"):
|
|
|
|
transfergroup_dict["valid_by__trigramme"] = (
|
|
|
|
transfergroup.valid_by and transfergroup.valid_by.trigramme or None
|
|
|
|
)
|
|
|
|
|
|
|
|
for transfer in transfergroup.filtered_transfers:
|
|
|
|
transfer_dict = {
|
|
|
|
"id": transfer.id,
|
|
|
|
"amount": transfer.amount,
|
|
|
|
"canceled_at": transfer.canceled_at,
|
|
|
|
"from_acc": transfer.from_acc.trigramme,
|
|
|
|
"to_acc": transfer.to_acc.trigramme,
|
|
|
|
}
|
|
|
|
if request.user.has_perm("kfet.is_team"):
|
|
|
|
transfer_dict["canceled_by__trigramme"] = (
|
|
|
|
transfer.canceled_by and transfer.canceled_by.trigramme or None
|
|
|
|
)
|
2019-12-25 17:28:36 +01:00
|
|
|
transfergroup_dict["entries"].append(transfer_dict)
|
|
|
|
history_groups.append(transfergroup_dict)
|
2019-12-23 11:07:02 +01:00
|
|
|
|
2019-12-25 17:28:36 +01:00
|
|
|
history_groups.sort(key=lambda group: group["at"])
|
2019-12-23 11:07:02 +01:00
|
|
|
|
2019-12-25 17:28:36 +01:00
|
|
|
return JsonResponse({"groups": history_groups})
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-14 23:37:05 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-14 23:37:05 +02:00
|
|
|
def kpsul_articles_data(request):
|
2018-10-06 12:35:49 +02:00
|
|
|
articles = Article.objects.values(
|
|
|
|
"id",
|
|
|
|
"name",
|
|
|
|
"price",
|
|
|
|
"stock",
|
|
|
|
"category_id",
|
|
|
|
"category__name",
|
|
|
|
"category__has_addcost",
|
2019-11-27 14:14:42 +01:00
|
|
|
"category__has_reduction",
|
2018-10-06 12:35:49 +02:00
|
|
|
).filter(is_sold=True)
|
|
|
|
return JsonResponse({"articles": list(articles)})
|
2016-08-22 03:57:13 +02:00
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-24 19:52:07 +02:00
|
|
|
def history(request):
|
2022-05-20 12:08:59 +02:00
|
|
|
# These limits are only useful for JS datepickers
|
|
|
|
# They don't enforce anything and can be bypassed
|
|
|
|
# Serious checks are done in history_json
|
|
|
|
history_limit, history_no_limit = get_history_limit(request.user)
|
|
|
|
history_no_limit_account_ids = Account.objects.filter(
|
|
|
|
trigramme__in=settings.KFET_HISTORY_NO_DATE_LIMIT_TRIGRAMMES
|
|
|
|
).values_list("id", flat=True)
|
|
|
|
format_date = lambda date: date.strftime("%Y-%m-%d %H:%M")
|
2021-02-10 22:13:50 +01:00
|
|
|
data = {
|
|
|
|
"filter_form": FilterHistoryForm(),
|
2022-05-20 12:08:59 +02:00
|
|
|
"history_limit": format_date(history_limit),
|
|
|
|
"history_no_limit_account_ids": history_no_limit_account_ids,
|
|
|
|
"history_no_limit": format_date(history_no_limit),
|
2021-02-10 22:13:50 +01:00
|
|
|
}
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(request, "kfet/history.html", data)
|
2016-08-24 19:52:07 +02:00
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
2016-08-22 03:57:13 +02:00
|
|
|
# -----
|
|
|
|
# Settings views
|
|
|
|
# -----
|
|
|
|
|
2017-04-03 20:32:16 +02:00
|
|
|
|
|
|
|
class SettingsList(TemplateView):
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/settings.html"
|
2016-08-22 03:57:13 +02:00
|
|
|
|
2016-08-22 04:21:10 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
config_list = permission_required("kfet.see_config")(SettingsList.as_view())
|
2017-08-16 21:28:16 +02:00
|
|
|
|
|
|
|
|
2019-12-02 20:41:19 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2017-04-03 20:32:16 +02:00
|
|
|
class SettingsUpdate(SuccessMessageMixin, FormView):
|
|
|
|
form_class = KFetConfigForm
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/settings_update.html"
|
|
|
|
success_message = "Paramètres mis à jour"
|
|
|
|
success_url = reverse_lazy("kfet.settings")
|
2016-08-22 03:57:13 +02:00
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_config"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-22 03:57:13 +02:00
|
|
|
return self.form_invalid(form)
|
2017-04-03 20:32:16 +02:00
|
|
|
form.save()
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
2016-08-22 03:57:13 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
config_update = permission_required("kfet.change_config")(SettingsUpdate.as_view())
|
2017-08-16 21:28:16 +02:00
|
|
|
|
2016-08-22 03:57:13 +02:00
|
|
|
|
2016-08-26 15:30:40 +02:00
|
|
|
# -----
|
|
|
|
# Transfer views
|
|
|
|
# -----
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2019-12-26 18:58:55 +01:00
|
|
|
@method_decorator(teamkfet_required, name="dispatch")
|
|
|
|
class TransferView(TemplateView):
|
|
|
|
template_name = "kfet/transfers.html"
|
2016-08-26 20:14:00 +02:00
|
|
|
|
2017-04-06 18:42:00 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2016-08-26 20:14:00 +02:00
|
|
|
def transfers_create(request):
|
2016-08-26 15:30:40 +02:00
|
|
|
transfer_formset = TransferFormSet(queryset=Transfer.objects.none())
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(
|
|
|
|
request, "kfet/transfers_create.html", {"transfer_formset": transfer_formset}
|
|
|
|
)
|
|
|
|
|
2016-08-26 15:30:40 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-26 15:30:40 +02:00
|
|
|
def perform_transfers(request):
|
2021-02-28 02:02:31 +01:00
|
|
|
data = {"errors": []}
|
2016-08-26 15:30:40 +02:00
|
|
|
|
|
|
|
# Checking transfer_formset
|
|
|
|
transfer_formset = TransferFormSet(request.POST)
|
2021-02-28 02:02:31 +01:00
|
|
|
try:
|
|
|
|
if not transfer_formset.is_valid():
|
|
|
|
for form_errors in transfer_formset.errors:
|
|
|
|
for (field, errors) in form_errors.items():
|
|
|
|
if field == "amount":
|
|
|
|
for error in errors:
|
|
|
|
data["errors"].append({"code": "amount", "message": error})
|
|
|
|
else:
|
|
|
|
# C'est compliqué de trouver le compte qui pose problème...
|
|
|
|
acc_error = True
|
|
|
|
|
|
|
|
if acc_error:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "invalid_acc",
|
|
|
|
"message": "L'un des comptes est invalide ou manquant",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
|
|
|
except ValidationError:
|
|
|
|
data["errors"].append(
|
|
|
|
{"code": "invalid_request", "message": "Requête invalide"}
|
|
|
|
)
|
|
|
|
return JsonResponse(data, status=400)
|
2016-08-26 15:30:40 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
transfers = transfer_formset.save(commit=False)
|
2016-08-26 15:30:40 +02:00
|
|
|
|
|
|
|
# Initializing vars
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms = set(
|
|
|
|
["kfet.add_transfer"]
|
|
|
|
) # Required perms to perform all transfers
|
2021-02-28 02:02:31 +01:00
|
|
|
to_accounts_balances = defaultdict(int) # For balances of accounts
|
2016-08-26 15:30:40 +02:00
|
|
|
|
|
|
|
for transfer in transfers:
|
|
|
|
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
|
|
|
to_accounts_balances[transfer.to_acc] += transfer.amount
|
|
|
|
|
2016-09-03 18:32:12 +02:00
|
|
|
negative_accounts = []
|
2016-08-26 15:30:40 +02:00
|
|
|
# Checking if ok on all accounts
|
2021-02-24 00:25:48 +01:00
|
|
|
frozen = set()
|
2016-08-26 15:30:40 +02:00
|
|
|
for account in to_accounts_balances:
|
2021-02-24 00:25:48 +01:00
|
|
|
if account.is_frozen:
|
|
|
|
frozen.add(account.trigramme)
|
|
|
|
|
2016-08-26 15:30:40 +02:00
|
|
|
(perms, stop) = account.perms_to_perform_operation(
|
2018-10-06 12:35:49 +02:00
|
|
|
amount=to_accounts_balances[account]
|
|
|
|
)
|
2016-08-26 15:30:40 +02:00
|
|
|
required_perms |= perms
|
2016-09-03 18:32:12 +02:00
|
|
|
if stop:
|
|
|
|
negative_accounts.append(account.trigramme)
|
2016-08-26 15:30:40 +02:00
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if frozen:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "frozen",
|
|
|
|
"message": "Les comptes suivants sont gelés : {}".format(
|
|
|
|
", ".join(frozen)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if negative_accounts:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "negative",
|
|
|
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
|
|
|
", ".join(negative_accounts)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if data["errors"]:
|
2021-02-24 00:25:48 +01:00
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if not request.user.has_perms(required_perms):
|
|
|
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
2016-08-26 15:30:40 +02:00
|
|
|
return JsonResponse(data, status=403)
|
|
|
|
|
2016-08-26 20:14:00 +02:00
|
|
|
# Creating transfer group
|
|
|
|
transfergroup = TransferGroup()
|
|
|
|
if required_perms:
|
|
|
|
transfergroup.valid_by = request.user.profile.account_kfet
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
comment = request.POST.get("comment", "")
|
2016-08-26 20:14:00 +02:00
|
|
|
transfergroup.comment = comment.strip()
|
|
|
|
|
2016-08-26 15:30:40 +02:00
|
|
|
with transaction.atomic():
|
|
|
|
# Updating balances accounts
|
|
|
|
for account in to_accounts_balances:
|
|
|
|
Account.objects.filter(pk=account.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
balance=F("balance") + to_accounts_balances[account]
|
|
|
|
)
|
2016-08-26 15:30:40 +02:00
|
|
|
account.refresh_from_db()
|
2021-02-18 17:57:59 +01:00
|
|
|
account.update_negative()
|
2016-08-26 15:30:40 +02:00
|
|
|
|
2016-08-26 20:14:00 +02:00
|
|
|
# Saving transfer group
|
2016-08-26 15:30:40 +02:00
|
|
|
transfergroup.save()
|
|
|
|
|
|
|
|
# Saving all transfers with group
|
|
|
|
for transfer in transfers:
|
|
|
|
transfer.group = transfergroup
|
|
|
|
transfer.save()
|
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
return JsonResponse({})
|
2016-08-27 14:12:01 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-09-24 14:18:26 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-09-24 14:18:26 +02:00
|
|
|
def cancel_transfers(request):
|
|
|
|
# Pour la réponse
|
2021-02-28 02:02:31 +01:00
|
|
|
data = {"canceled": [], "warnings": {}, "errors": []}
|
2016-09-24 14:18:26 +02:00
|
|
|
|
|
|
|
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
|
|
|
try:
|
|
|
|
# Set pour virer les doublons
|
2018-10-06 12:35:49 +02:00
|
|
|
transfers_post = set(
|
|
|
|
map(int, filter(None, request.POST.getlist("transfers[]", [])))
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
except ValueError:
|
2021-02-28 02:02:31 +01:00
|
|
|
data["errors"].append(
|
|
|
|
{"code": "invalid_request", "message": "Requête invalide !"}
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
return JsonResponse(data, status=400)
|
2021-02-28 02:02:31 +01:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
transfers_all = Transfer.objects.select_related(
|
|
|
|
"group", "from_acc", "from_acc__negative", "to_acc", "to_acc__negative"
|
|
|
|
).filter(pk__in=transfers_post)
|
|
|
|
transfers_pk = [transfer.pk for transfer in transfers_all]
|
|
|
|
transfers_notexisting = [
|
|
|
|
transfer for transfer in transfers_post if transfer not in transfers_pk
|
|
|
|
]
|
2016-09-24 14:18:26 +02:00
|
|
|
if transfers_notexisting:
|
2021-02-28 02:02:31 +01:00
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "cancel_missing",
|
|
|
|
"message": "Transferts inexistants : {}".format(
|
|
|
|
", ".join(map(str, transfers_notexisting))
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
transfers_already_canceled = [] # Déjà annulés
|
|
|
|
transfers = [] # Pas déjà annulés
|
2016-09-24 14:18:26 +02:00
|
|
|
required_perms = set()
|
2017-04-03 20:32:16 +02:00
|
|
|
cancel_duration = kfet_config.cancel_duration
|
2021-02-28 02:02:31 +01:00
|
|
|
|
|
|
|
# Modifs à faire sur les balances des comptes
|
|
|
|
to_accounts_balances = defaultdict(int)
|
2016-09-24 14:18:26 +02:00
|
|
|
for transfer in transfers_all:
|
|
|
|
if transfer.canceled_at:
|
|
|
|
# Transfert déjà annulé, va pour un warning en Response
|
|
|
|
transfers_already_canceled.append(transfer.pk)
|
|
|
|
else:
|
|
|
|
transfers.append(transfer.pk)
|
|
|
|
# Si transfer il y a plus de CANCEL_DURATION, permission requise
|
|
|
|
if transfer.group.at + cancel_duration < timezone.now():
|
2018-10-06 12:35:49 +02:00
|
|
|
required_perms.add("kfet.cancel_old_operations")
|
2016-09-24 14:18:26 +02:00
|
|
|
|
|
|
|
# Calcul de toutes modifs à faire en cas de validation
|
|
|
|
|
|
|
|
# Pour les balances de comptes
|
|
|
|
to_accounts_balances[transfer.from_acc] += transfer.amount
|
|
|
|
to_accounts_balances[transfer.to_acc] += -transfer.amount
|
|
|
|
|
|
|
|
if not transfers:
|
2018-10-06 12:35:49 +02:00
|
|
|
data["warnings"]["already_canceled"] = transfers_already_canceled
|
2016-09-24 14:18:26 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
|
|
|
negative_accounts = []
|
|
|
|
# Checking permissions or stop
|
|
|
|
for account in to_accounts_balances:
|
|
|
|
(perms, stop) = account.perms_to_perform_operation(
|
2018-10-06 12:35:49 +02:00
|
|
|
amount=to_accounts_balances[account]
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
required_perms |= perms
|
|
|
|
if stop:
|
|
|
|
negative_accounts.append(account.trigramme)
|
|
|
|
|
2021-02-28 02:02:31 +01:00
|
|
|
if negative_accounts:
|
|
|
|
data["errors"].append(
|
|
|
|
{
|
|
|
|
"code": "negative",
|
|
|
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
|
|
|
", ".join(negative_accounts)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return JsonResponse(data, status=400)
|
|
|
|
|
|
|
|
if not request.user.has_perms(required_perms):
|
|
|
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
2016-09-24 14:18:26 +02:00
|
|
|
return JsonResponse(data, status=403)
|
|
|
|
|
|
|
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
|
|
|
canceled_at = timezone.now()
|
|
|
|
|
|
|
|
with transaction.atomic():
|
2018-10-06 12:35:49 +02:00
|
|
|
(
|
|
|
|
Transfer.objects.filter(pk__in=transfers).update(
|
|
|
|
canceled_by=canceled_by, canceled_at=canceled_at
|
|
|
|
)
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
|
|
|
|
for account in to_accounts_balances:
|
|
|
|
Account.objects.filter(pk=account.pk).update(
|
2018-10-06 12:35:49 +02:00
|
|
|
balance=F("balance") + to_accounts_balances[account]
|
|
|
|
)
|
2016-09-24 14:18:26 +02:00
|
|
|
account.refresh_from_db()
|
2021-02-18 17:57:59 +01:00
|
|
|
account.update_negative()
|
2016-09-24 14:18:26 +02:00
|
|
|
|
2019-12-23 15:09:41 +01:00
|
|
|
transfers = (
|
|
|
|
Transfer.objects.values("id", "canceled_at", "canceled_by__trigramme")
|
|
|
|
.filter(pk__in=transfers)
|
|
|
|
.order_by("pk")
|
|
|
|
)
|
2019-12-23 18:55:15 +01:00
|
|
|
data["canceled"] = list(transfers)
|
2016-09-24 14:18:26 +02:00
|
|
|
if transfers_already_canceled:
|
2018-10-06 12:35:49 +02:00
|
|
|
data["warnings"]["already_canceled"] = transfers_already_canceled
|
2016-09-24 14:18:26 +02:00
|
|
|
return JsonResponse(data)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-27 14:12:01 +02:00
|
|
|
class InventoryList(ListView):
|
2018-10-06 12:35:49 +02:00
|
|
|
queryset = (
|
|
|
|
Inventory.objects.select_related("by", "order")
|
|
|
|
.annotate(nb_articles=Count("articles"))
|
|
|
|
.order_by("-at")
|
|
|
|
)
|
|
|
|
template_name = "kfet/inventory.html"
|
|
|
|
context_object_name = "inventories"
|
|
|
|
|
2016-08-27 14:12:01 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-27 14:12:01 +02:00
|
|
|
def inventory_create(request):
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
articles = Article.objects.select_related("category").order_by(
|
2020-12-09 22:03:54 +01:00
|
|
|
"-is_sold", "category__name", "name"
|
2018-10-06 12:35:49 +02:00
|
|
|
)
|
2016-08-27 14:12:01 +02:00
|
|
|
initial = []
|
|
|
|
for article in articles:
|
2018-10-06 12:35:49 +02:00
|
|
|
initial.append(
|
|
|
|
{
|
2020-12-09 22:03:54 +01:00
|
|
|
"is_sold": article.is_sold,
|
2018-10-06 12:35:49 +02:00
|
|
|
"article": article.pk,
|
|
|
|
"stock_old": article.stock,
|
|
|
|
"name": article.name,
|
|
|
|
"category": article.category_id,
|
|
|
|
"category__name": article.category.name,
|
|
|
|
"box_capacity": article.box_capacity or 0,
|
|
|
|
}
|
2016-08-27 14:12:01 +02:00
|
|
|
)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
cls_formset = formset_factory(form=InventoryArticleForm, extra=0)
|
|
|
|
|
2016-08-27 14:12:01 +02:00
|
|
|
if request.POST:
|
|
|
|
formset = cls_formset(request.POST, initial=initial)
|
2016-08-27 22:55:31 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if not request.user.has_perm("kfet.add_inventory"):
|
2021-02-20 19:18:21 +01:00
|
|
|
messages.error(
|
|
|
|
request, "Permission refusée", extra_tags="permission-denied"
|
|
|
|
)
|
2016-08-27 22:55:31 +02:00
|
|
|
elif formset.is_valid():
|
2016-08-27 14:12:01 +02:00
|
|
|
with transaction.atomic():
|
|
|
|
|
|
|
|
articles = Article.objects.select_for_update()
|
|
|
|
inventory = Inventory()
|
|
|
|
inventory.by = request.user.profile.account_kfet
|
|
|
|
saved = False
|
|
|
|
for form in formset:
|
2018-10-06 12:35:49 +02:00
|
|
|
if form.cleaned_data["stock_new"] is not None:
|
2016-08-27 14:12:01 +02:00
|
|
|
if not saved:
|
|
|
|
inventory.save()
|
2016-08-28 05:39:34 +02:00
|
|
|
saved = True
|
2016-08-27 14:12:01 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
article = articles.get(pk=form.cleaned_data["article"].pk)
|
2016-08-27 14:12:01 +02:00
|
|
|
stock_old = article.stock
|
2018-10-06 12:35:49 +02:00
|
|
|
stock_new = form.cleaned_data["stock_new"]
|
2016-08-27 14:12:01 +02:00
|
|
|
InventoryArticle.objects.create(
|
2018-10-06 12:35:49 +02:00
|
|
|
inventory=inventory,
|
|
|
|
article=article,
|
|
|
|
stock_old=stock_old,
|
|
|
|
stock_new=stock_new,
|
|
|
|
)
|
2016-08-27 14:12:01 +02:00
|
|
|
article.stock = stock_new
|
|
|
|
article.save()
|
2016-08-27 22:55:31 +02:00
|
|
|
if saved:
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.success(request, "Inventaire créé")
|
|
|
|
return redirect("kfet.inventory")
|
|
|
|
messages.warning(request, "Bah alors ? On a rien compté ?")
|
2016-08-27 14:12:01 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.error(request, "Pas marché")
|
2016-08-27 14:12:01 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
formset = cls_formset(initial=initial)
|
|
|
|
|
|
|
|
return render(request, "kfet/inventory_create.html", {"formset": formset})
|
2016-08-27 14:12:01 +02:00
|
|
|
|
2016-08-27 22:55:31 +02:00
|
|
|
|
2016-08-30 23:32:54 +02:00
|
|
|
class InventoryRead(DetailView):
|
|
|
|
model = Inventory
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/inventory_read.html"
|
|
|
|
context_object_name = "inventory"
|
2016-08-30 23:32:54 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
inventoryarticles = (
|
|
|
|
InventoryArticle.objects.select_related("article", "article__category")
|
|
|
|
.filter(inventory=self.object)
|
|
|
|
.order_by("article__category__name", "article__name")
|
|
|
|
)
|
|
|
|
context["inventoryarts"] = inventoryarticles
|
2016-08-30 23:32:54 +02:00
|
|
|
return context
|
2016-08-27 22:55:31 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2020-09-11 15:21:29 +02:00
|
|
|
class InventoryDelete(PermissionRequiredMixin, DeleteView):
|
|
|
|
model = Inventory
|
|
|
|
success_url = reverse_lazy("kfet.inventory")
|
|
|
|
success_message = "Inventaire annulé avec succès !"
|
|
|
|
permission_required = "kfet.delete_inventory"
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
return redirect("kfet.inventory.read", self.kwargs.get(self.pk_url_kwarg))
|
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
|
|
inv = self.get_object()
|
|
|
|
# On met à jour les articles dont c'est le dernier inventaire
|
|
|
|
# .get() ne marche pas avec OuterRef, donc on utilise .filter() avec [:1]
|
|
|
|
update_subquery = InventoryArticle.objects.filter(
|
|
|
|
inventory=inv, article=OuterRef("pk")
|
|
|
|
).values("stock_old")[:1]
|
|
|
|
|
|
|
|
Article.objects.annotate(last_env=Max("inventories__at")).filter(
|
|
|
|
last_env=inv.at
|
|
|
|
).update(stock=Subquery(update_subquery))
|
|
|
|
|
|
|
|
# On a tout mis à jour, on peut delete (avec un message)
|
|
|
|
messages.success(request, self.success_message)
|
|
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2016-08-27 22:55:31 +02:00
|
|
|
# -----
|
|
|
|
# Order views
|
|
|
|
# -----
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-08-27 22:55:31 +02:00
|
|
|
class OrderList(ListView):
|
2018-10-06 12:35:49 +02:00
|
|
|
queryset = Order.objects.select_related("supplier", "inventory")
|
|
|
|
template_name = "kfet/order.html"
|
|
|
|
context_object_name = "orders"
|
2016-08-27 22:55:31 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
context["suppliers"] = Supplier.objects.order_by("name")
|
2016-08-27 22:55:31 +02:00
|
|
|
return context
|
|
|
|
|
2017-04-13 14:11:44 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-28 05:39:34 +02:00
|
|
|
def order_create(request, pk):
|
2016-08-30 15:35:30 +02:00
|
|
|
supplier = get_object_or_404(Supplier, pk=pk)
|
2016-08-28 05:39:34 +02:00
|
|
|
|
2017-04-13 14:11:44 +02:00
|
|
|
articles = (
|
2018-10-06 12:35:49 +02:00
|
|
|
Article.objects.filter(suppliers=supplier.pk)
|
2017-04-13 14:11:44 +02:00
|
|
|
.distinct()
|
2018-10-06 12:35:49 +02:00
|
|
|
.select_related("category")
|
2020-12-09 22:03:54 +01:00
|
|
|
.order_by("-is_sold", "category__name", "name")
|
2017-04-13 14:11:44 +02:00
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
|
2017-04-13 14:11:44 +02:00
|
|
|
# Force hit to cache
|
|
|
|
articles = list(articles)
|
|
|
|
|
|
|
|
sales_q = (
|
2018-10-06 12:35:49 +02:00
|
|
|
Operation.objects.select_related("group")
|
2016-08-28 05:39:34 +02:00
|
|
|
.filter(article__in=articles, canceled_at=None)
|
2018-10-06 12:35:49 +02:00
|
|
|
.values("article")
|
|
|
|
.annotate(nb=Sum("article_nb"))
|
2016-08-28 05:39:34 +02:00
|
|
|
)
|
2017-04-13 14:11:44 +02:00
|
|
|
scale = WeekScale(last=True, n_steps=5, std_chunk=False)
|
2018-10-06 12:35:49 +02:00
|
|
|
chunks = scale.chunkify_qs(sales_q, field="group__at")
|
2017-04-13 14:11:44 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
sales = [{d["article"]: d["nb"] for d in chunk} for chunk in chunks]
|
2017-04-13 14:11:44 +02:00
|
|
|
|
|
|
|
initial = []
|
2016-08-28 05:39:34 +02:00
|
|
|
|
|
|
|
for article in articles:
|
2017-04-13 14:11:44 +02:00
|
|
|
# Get sales for each 5 last weeks
|
2017-05-10 13:11:47 +02:00
|
|
|
v_all = [chunk.get(article.pk, 0) for chunk in sales]
|
2017-04-13 14:11:44 +02:00
|
|
|
# Take the 3 greatest (eg to avoid 2 weeks of vacations)
|
2016-08-28 05:39:34 +02:00
|
|
|
v_3max = heapq.nlargest(3, v_all)
|
2017-04-13 14:11:44 +02:00
|
|
|
# Get average and standard deviation
|
2016-08-28 05:39:34 +02:00
|
|
|
v_moy = statistics.mean(v_3max)
|
|
|
|
v_et = statistics.pstdev(v_3max, v_moy)
|
2017-04-13 14:11:44 +02:00
|
|
|
# Expected sales for next week
|
2016-08-28 05:39:34 +02:00
|
|
|
v_prev = v_moy + v_et
|
2017-04-13 14:11:44 +02:00
|
|
|
# We want to have 1.5 * the expected sales in stock
|
|
|
|
# (because sometimes some articles are not delivered)
|
2016-08-28 05:39:34 +02:00
|
|
|
c_rec_tot = max(v_prev * 1.5 - article.stock, 0)
|
2017-04-13 14:11:44 +02:00
|
|
|
# If ordered quantity is close enough to a level which can led to free
|
|
|
|
# boxes, we increase it to this level.
|
2016-08-28 05:39:34 +02:00
|
|
|
if article.box_capacity:
|
2016-08-30 15:35:30 +02:00
|
|
|
c_rec_temp = c_rec_tot / article.box_capacity
|
2016-08-28 05:39:34 +02:00
|
|
|
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)
|
2018-10-06 12:35:49 +02:00
|
|
|
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_all": v_all,
|
|
|
|
"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),
|
2020-12-09 22:22:12 +01:00
|
|
|
"is_sold": article.is_sold,
|
2018-10-06 12:35:49 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
cls_formset = formset_factory(form=OrderArticleForm, extra=0)
|
2016-08-28 05:39:34 +02:00
|
|
|
|
|
|
|
if request.POST:
|
|
|
|
formset = cls_formset(request.POST, initial=initial)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if not request.user.has_perm("kfet.add_order"):
|
2021-02-20 19:18:21 +01:00
|
|
|
messages.error(
|
|
|
|
request, "Permission refusée", extra_tags="permission-denied"
|
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
elif formset.is_valid():
|
|
|
|
order = Order()
|
|
|
|
order.supplier = supplier
|
|
|
|
saved = False
|
|
|
|
for form in formset:
|
2018-10-06 12:35:49 +02:00
|
|
|
if form.cleaned_data["quantity_ordered"] is not None:
|
2016-08-28 05:39:34 +02:00
|
|
|
if not saved:
|
|
|
|
order.save()
|
|
|
|
saved = True
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
article = form.cleaned_data["article"]
|
|
|
|
q_ordered = form.cleaned_data["quantity_ordered"]
|
2016-08-28 05:39:34 +02:00
|
|
|
if article.box_capacity:
|
2016-08-30 15:35:30 +02:00
|
|
|
q_ordered *= article.box_capacity
|
2016-08-28 05:39:34 +02:00
|
|
|
OrderArticle.objects.create(
|
2018-10-06 12:35:49 +02:00
|
|
|
order=order, article=article, quantity_ordered=q_ordered
|
2017-04-13 14:11:44 +02:00
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
if saved:
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.success(request, "Commande créée")
|
|
|
|
return redirect("kfet.order.read", order.pk)
|
|
|
|
messages.warning(request, "Rien commandé => Pas de commande")
|
2016-08-28 05:39:34 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
messages.error(request, "Corrigez les erreurs")
|
2016-08-28 05:39:34 +02:00
|
|
|
else:
|
|
|
|
formset = cls_formset(initial=initial)
|
|
|
|
|
kfet -- Tables are sortable
Many tables in kfet app templates become sortable:
account list, negative account list, article list, article inventory
list, article supplier list, article category list, checkout list,
checkout statement list, inventory list, inventory details, order list,
order creation, order details.
This is achieved thanks to the jQuery plugin 'tablesorter':
https://mottie.github.io/tablesorter/docs/
- Affected tables also got sticky headers (it stays visible on scroll).
- Dates format are modified in order to ease the date sorting with the
plugin (it avoids writing a custom parser, or an extractor from
additional hidden element in the table cells).
- Tables whose content is classified by category (of articles) now uses
several tbodies. This has minor effects on the tables style.
- Tags of the header help signs become 'i', instead of 'span', in order
to avoid weird spacing.
2017-11-27 18:24:22 +01:00
|
|
|
scale.label_fmt = "S-{rev_i}"
|
2017-05-19 17:40:06 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"kfet/order_create.html",
|
|
|
|
{"supplier": supplier, "formset": formset, "scale": scale},
|
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
|
2017-04-13 14:11:44 +02:00
|
|
|
|
2016-08-28 05:39:34 +02:00
|
|
|
class OrderRead(DetailView):
|
|
|
|
model = Order
|
2018-10-06 12:35:49 +02:00
|
|
|
template_name = "kfet/order_read.html"
|
|
|
|
context_object_name = "order"
|
2016-08-28 05:39:34 +02:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-01-16 16:22:52 +01:00
|
|
|
context = super().get_context_data(**kwargs)
|
2018-10-06 12:35:49 +02:00
|
|
|
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 :"
|
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
category = 0
|
|
|
|
for orderarticle in orderarticles:
|
|
|
|
if category != orderarticle.article.category:
|
|
|
|
category = orderarticle.article.category
|
2018-10-06 12:35:49 +02:00
|
|
|
mail += "\n"
|
2016-08-28 05:39:34 +02:00
|
|
|
nb = orderarticle.quantity_ordered
|
2018-10-06 12:35:49 +02:00
|
|
|
box = ""
|
2016-08-28 05:39:34 +02:00
|
|
|
if orderarticle.article.box_capacity:
|
|
|
|
nb /= orderarticle.article.box_capacity
|
|
|
|
if nb >= 2:
|
2018-10-06 12:35:49 +02:00
|
|
|
box = " %ss de" % orderarticle.article.box_type
|
2016-08-28 05:39:34 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
box = " %s de" % orderarticle.article.box_type
|
2016-08-28 05:39:34 +02:00
|
|
|
name = orderarticle.article.name.capitalize()
|
|
|
|
mail += "\n- %s%s %s" % (round(nb), box, name)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
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"
|
|
|
|
)
|
2016-08-28 05:39:34 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
context["mail"] = mail
|
2016-08-28 05:39:34 +02:00
|
|
|
return context
|
|
|
|
|
2017-04-13 16:34:29 +02:00
|
|
|
|
2016-08-31 02:52:13 +02:00
|
|
|
@teamkfet_required
|
2019-11-21 01:18:38 +01:00
|
|
|
@kfet_password_auth
|
2016-08-30 15:35:30 +02:00
|
|
|
def order_to_inventory(request, pk):
|
|
|
|
order = get_object_or_404(Order, pk=pk)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if hasattr(order, "inventory"):
|
2016-08-30 15:35:30 +02:00
|
|
|
raise Http404
|
|
|
|
|
2017-04-13 16:34:29 +02:00
|
|
|
supplier_prefetch = Prefetch(
|
2018-10-06 12:35:49 +02:00
|
|
|
"article__supplierarticle_set",
|
2017-04-13 16:34:29 +02:00
|
|
|
queryset=(
|
2018-10-06 12:35:49 +02:00
|
|
|
SupplierArticle.objects.filter(supplier=order.supplier).order_by("-at")
|
2017-04-13 16:34:29 +02:00
|
|
|
),
|
2018-10-06 12:35:49 +02:00
|
|
|
to_attr="supplier",
|
2017-04-13 16:34:29 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
order_articles = (
|
2018-10-06 12:35:49 +02:00
|
|
|
OrderArticle.objects.filter(order=order.pk)
|
|
|
|
.select_related("article", "article__category")
|
|
|
|
.prefetch_related(supplier_prefetch)
|
|
|
|
.order_by("article__category__name", "article__name")
|
2017-04-13 16:34:29 +02:00
|
|
|
)
|
2016-08-30 15:35:30 +02:00
|
|
|
|
|
|
|
initial = []
|
2017-04-13 16:34:29 +02:00
|
|
|
for order_article in order_articles:
|
|
|
|
article = order_article.article
|
2018-10-06 12:35:49 +02:00
|
|
|
initial.append(
|
|
|
|
{
|
|
|
|
"article": article.pk,
|
|
|
|
"name": article.name,
|
|
|
|
"category": article.category_id,
|
|
|
|
"category__name": article.category.name,
|
|
|
|
"quantity_ordered": order_article.quantity_ordered,
|
|
|
|
"quantity_received": order_article.quantity_ordered,
|
|
|
|
"price_HT": article.supplier[0].price_HT,
|
|
|
|
"TVA": article.supplier[0].TVA,
|
|
|
|
"rights": article.supplier[0].rights,
|
|
|
|
}
|
|
|
|
)
|
2016-08-30 15:35:30 +02:00
|
|
|
|
|
|
|
cls_formset = formset_factory(OrderArticleToInventoryForm, extra=0)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if request.method == "POST":
|
2016-08-30 15:35:30 +02:00
|
|
|
formset = cls_formset(request.POST, initial=initial)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
if not request.user.has_perm("kfet.order_to_inventory"):
|
2021-02-20 19:18:21 +01:00
|
|
|
messages.error(
|
|
|
|
request, "Permission refusée", extra_tags="permission-denied"
|
|
|
|
)
|
2016-08-30 15:35:30 +02:00
|
|
|
elif formset.is_valid():
|
|
|
|
with transaction.atomic():
|
2017-04-13 16:34:29 +02:00
|
|
|
inventory = Inventory.objects.create(
|
2018-10-06 12:35:49 +02:00
|
|
|
order=order, by=request.user.profile.account_kfet
|
2017-04-13 16:34:29 +02:00
|
|
|
)
|
|
|
|
new_supplierarticle = []
|
|
|
|
new_inventoryarticle = []
|
2016-08-30 15:35:30 +02:00
|
|
|
for form in formset:
|
2018-10-06 12:35:49 +02:00
|
|
|
q_received = form.cleaned_data["quantity_received"]
|
|
|
|
article = form.cleaned_data["article"]
|
|
|
|
|
|
|
|
price_HT = form.cleaned_data["price_HT"]
|
|
|
|
TVA = form.cleaned_data["TVA"]
|
|
|
|
rights = form.cleaned_data["rights"]
|
|
|
|
|
|
|
|
if any(
|
|
|
|
(
|
|
|
|
form.initial["price_HT"] != price_HT,
|
|
|
|
form.initial["TVA"] != TVA,
|
|
|
|
form.initial["rights"] != rights,
|
|
|
|
)
|
|
|
|
):
|
2017-04-13 16:34:29 +02:00
|
|
|
new_supplierarticle.append(
|
|
|
|
SupplierArticle(
|
|
|
|
supplier=order.supplier,
|
|
|
|
article=article,
|
|
|
|
price_HT=price_HT,
|
|
|
|
TVA=TVA,
|
|
|
|
rights=rights,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
(
|
2018-10-06 12:35:49 +02:00
|
|
|
OrderArticle.objects.filter(
|
|
|
|
order=order, article=article
|
|
|
|
).update(quantity_received=q_received)
|
2017-04-13 16:34:29 +02:00
|
|
|
)
|
|
|
|
new_inventoryarticle.append(
|
|
|
|
InventoryArticle(
|
|
|
|
inventory=inventory,
|
|
|
|
article=article,
|
|
|
|
stock_old=article.stock,
|
|
|
|
stock_new=article.stock + q_received,
|
|
|
|
)
|
|
|
|
)
|
2016-08-30 15:35:30 +02:00
|
|
|
article.stock += q_received
|
2016-08-31 01:06:48 +02:00
|
|
|
if q_received > 0:
|
|
|
|
article.is_sold = True
|
2016-08-30 15:35:30 +02:00
|
|
|
article.save()
|
2017-04-13 16:34:29 +02:00
|
|
|
SupplierArticle.objects.bulk_create(new_supplierarticle)
|
|
|
|
InventoryArticle.objects.bulk_create(new_inventoryarticle)
|
2016-08-30 15:35:30 +02:00
|
|
|
messages.success(request, "C'est tout bon !")
|
2018-10-06 12:35:49 +02:00
|
|
|
return redirect("kfet.order")
|
2016-08-30 15:35:30 +02:00
|
|
|
else:
|
|
|
|
messages.error(request, "Corrigez les erreurs")
|
|
|
|
else:
|
|
|
|
formset = cls_formset(initial=initial)
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
return render(
|
|
|
|
request, "kfet/order_to_inventory.html", {"formset": formset, "order": order}
|
|
|
|
)
|
|
|
|
|
2016-08-30 15:35:30 +02:00
|
|
|
|
2019-11-21 01:18:38 +01:00
|
|
|
@method_decorator(kfet_password_auth, name="dispatch")
|
2016-08-27 22:55:31 +02:00
|
|
|
class SupplierUpdate(SuccessMessageMixin, UpdateView):
|
2018-10-06 12:35:49 +02:00
|
|
|
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"
|
2016-08-27 22:55:31 +02:00
|
|
|
|
|
|
|
# Surcharge de la validation
|
|
|
|
def form_valid(self, form):
|
|
|
|
# Checking permission
|
2018-10-06 12:35:49 +02:00
|
|
|
if not self.request.user.has_perm("kfet.change_supplier"):
|
2021-02-20 19:18:21 +01:00
|
|
|
form.add_error(
|
|
|
|
None, ValidationError("Permission refusée", code="permission-denied")
|
|
|
|
)
|
2016-08-27 22:55:31 +02:00
|
|
|
return self.form_invalid(form)
|
|
|
|
# Updating
|
2018-01-16 16:22:52 +01:00
|
|
|
return super().form_valid(form)
|
2016-12-09 21:45:34 +01:00
|
|
|
|
|
|
|
|
2016-12-10 17:33:24 +01:00
|
|
|
# ==========
|
2016-12-09 21:45:34 +01:00
|
|
|
# Statistics
|
2016-12-10 17:33:24 +01:00
|
|
|
# ==========
|
2017-01-17 17:16:53 +01:00
|
|
|
|
|
|
|
# ---------------
|
|
|
|
# Vues génériques
|
|
|
|
# ---------------
|
|
|
|
# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
|
2020-03-09 16:09:12 +01:00
|
|
|
class JSONResponseMixin:
|
2017-01-17 17:16:53 +01:00
|
|
|
"""
|
|
|
|
A mixin that can be used to render a JSON response.
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
def render_to_json_response(self, context, **response_kwargs):
|
|
|
|
"""
|
|
|
|
Returns a JSON response, transforming 'context' to make the payload.
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
return JsonResponse(self.get_data(context), **response_kwargs)
|
2017-01-17 17:16:53 +01:00
|
|
|
|
|
|
|
def get_data(self, context):
|
|
|
|
"""
|
|
|
|
Returns an object that will be serialized as JSON by json.dumps().
|
|
|
|
"""
|
|
|
|
# Note: This is *EXTREMELY* naive; in reality, you'll need
|
|
|
|
# to do much more complex handling to ensure that arbitrary
|
|
|
|
# objects -- such as Django model instances or querysets
|
|
|
|
# -- can be serialized as JSON.
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2017-04-02 05:34:34 +02:00
|
|
|
class JSONDetailView(JSONResponseMixin, BaseDetailView):
|
|
|
|
"""Returns a DetailView that renders a JSON."""
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-02-15 21:01:54 +01:00
|
|
|
def render_to_response(self, context):
|
|
|
|
return self.render_to_json_response(context)
|
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-04-02 17:03:20 +02:00
|
|
|
class SingleResumeStat(JSONDetailView):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Génère l'interface de sélection pour les statistiques d'un compte/article.
|
|
|
|
L'interface est constituée d'une série de boutons, qui récupèrent et graphent
|
|
|
|
des statistiques du même type, sur le même objet mais avec des arguments différents.
|
|
|
|
|
|
|
|
Attributs :
|
|
|
|
- url_stat : URL où récupérer les statistiques
|
|
|
|
- stats : liste de dictionnaires avec les clés suivantes :
|
|
|
|
- label : texte du bouton
|
|
|
|
- url_params : paramètres GET à rajouter à `url_stat`
|
|
|
|
- default : si `True`, graphe à montrer par défaut
|
|
|
|
|
|
|
|
On peut aussi définir `stats` dynamiquement, via la fonction `get_stats`.
|
2017-01-20 20:13:03 +01:00
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-04-02 17:03:20 +02:00
|
|
|
url_stat = None
|
2020-03-09 16:20:49 +01:00
|
|
|
stats = []
|
|
|
|
|
|
|
|
def get_stats(self):
|
|
|
|
return self.stats
|
2017-01-24 16:54:02 +01:00
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
2017-01-25 23:23:53 +01:00
|
|
|
# On n'hérite pas
|
2017-01-17 17:16:53 +01:00
|
|
|
context = {}
|
2017-04-02 17:03:20 +02:00
|
|
|
stats = []
|
2020-03-09 16:15:15 +01:00
|
|
|
# On peut avoir récupéré self.object via pk ou slug
|
|
|
|
if self.pk_url_kwarg in self.kwargs:
|
2017-04-02 17:03:20 +02:00
|
|
|
url_pk = getattr(self.object, self.pk_url_kwarg)
|
2020-03-09 16:15:15 +01:00
|
|
|
else:
|
|
|
|
url_pk = getattr(self.object, self.slug_url_kwarg)
|
|
|
|
|
|
|
|
for stat_def in self.get_stats():
|
2018-10-06 12:35:49 +02:00
|
|
|
url_params_d = stat_def.get("url_params", {})
|
2017-04-02 17:03:20 +02:00
|
|
|
if len(url_params_d) > 0:
|
2018-10-06 12:35:49 +02:00
|
|
|
url_params = "?{}".format(urlencode(url_params_d))
|
2017-04-02 17:03:20 +02:00
|
|
|
else:
|
2018-10-06 12:35:49 +02:00
|
|
|
url_params = ""
|
|
|
|
stats.append(
|
|
|
|
{
|
|
|
|
"label": stat_def["label"],
|
|
|
|
"url": "{url}{params}".format(
|
|
|
|
url=reverse(self.url_stat, args=[url_pk]), params=url_params
|
|
|
|
),
|
2020-03-09 16:20:49 +01:00
|
|
|
"default": stat_def.get("default", False),
|
2018-10-06 12:35:49 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
context["stats"] = stats
|
2017-01-17 17:16:53 +01:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
2020-03-09 16:09:12 +01:00
|
|
|
class UserAccountMixin:
|
|
|
|
"""
|
|
|
|
Mixin qui vérifie que le compte traité par la vue est celui de l'utilisateur·ice
|
|
|
|
actuel·le. Dans le cas contraire, renvoie un Http404.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_object(self, *args, **kwargs):
|
|
|
|
obj = super().get_object(*args, **kwargs)
|
|
|
|
if self.request.user != obj.user:
|
|
|
|
raise Http404
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
2020-09-16 17:16:49 +02:00
|
|
|
class ScaleMixin(object):
|
|
|
|
"""Mixin pour utiliser les outils de `kfet.statistic`."""
|
|
|
|
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
|
|
# On n'hérite pas
|
|
|
|
form = StatScaleForm(self.request.GET, prefix="scale")
|
|
|
|
|
|
|
|
if not form.is_valid():
|
|
|
|
raise SuspiciousOperation(
|
|
|
|
"Invalid StatScaleForm. Did someone tamper with the GET parameters ?"
|
|
|
|
)
|
|
|
|
|
|
|
|
scale_name = form.cleaned_data.pop("name")
|
|
|
|
scale_cls = SCALE_DICT.get(scale_name)
|
|
|
|
|
|
|
|
self.scale = scale_cls(**form.cleaned_data)
|
|
|
|
|
|
|
|
return {"labels": self.scale.get_labels()}
|
|
|
|
|
|
|
|
|
2016-12-21 11:51:08 +01:00
|
|
|
# -----------------------
|
|
|
|
# Evolution Balance perso
|
|
|
|
# -----------------------
|
|
|
|
|
|
|
|
|
2020-03-09 16:09:12 +01:00
|
|
|
@method_decorator(login_required, name="dispatch")
|
|
|
|
class AccountStatBalanceList(UserAccountMixin, SingleResumeStat):
|
|
|
|
"""
|
2020-03-09 15:06:55 +01:00
|
|
|
Menu général pour l'historique de balance d'un compte
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-12-21 11:51:08 +01:00
|
|
|
model = Account
|
2020-03-09 16:15:15 +01:00
|
|
|
slug_url_kwarg = "trigramme"
|
|
|
|
slug_field = "trigramme"
|
2018-10-06 12:35:49 +02:00
|
|
|
url_stat = "kfet.account.stat.balance"
|
2017-04-02 17:03:20 +02:00
|
|
|
stats = [
|
2018-10-06 12:35:49 +02:00
|
|
|
{"label": "Tout le temps"},
|
|
|
|
{"label": "1 an", "url_params": {"last_days": 365}},
|
|
|
|
{"label": "6 mois", "url_params": {"last_days": 183}},
|
2020-03-09 16:20:49 +01:00
|
|
|
{"label": "3 mois", "url_params": {"last_days": 90}, "default": True},
|
2018-10-06 12:35:49 +02:00
|
|
|
{"label": "30 jours", "url_params": {"last_days": 30}},
|
2017-04-02 17:03:20 +02:00
|
|
|
]
|
2016-12-21 11:51:08 +01:00
|
|
|
|
2017-04-03 17:06:32 +02:00
|
|
|
|
2020-03-09 16:09:12 +01:00
|
|
|
@method_decorator(login_required, name="dispatch")
|
|
|
|
class AccountStatBalance(UserAccountMixin, JSONDetailView):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Statistiques (JSON) d'historique de balance d'un compte.
|
|
|
|
Prend en compte les opérations et transferts sur la période donnée.
|
2017-01-20 20:13:03 +01:00
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
model = Account
|
2020-03-09 16:15:15 +01:00
|
|
|
slug_url_kwarg = "trigramme"
|
|
|
|
slug_field = "trigramme"
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-04-02 17:03:20 +02:00
|
|
|
def get_changes_list(self, last_days=None, begin_date=None, end_date=None):
|
2017-01-17 17:16:53 +01:00
|
|
|
account = self.object
|
2017-04-02 17:03:20 +02:00
|
|
|
|
|
|
|
# prepare filters
|
|
|
|
if last_days is not None:
|
2017-04-03 03:12:52 +02:00
|
|
|
end_date = timezone.now()
|
2017-04-02 17:03:20 +02:00
|
|
|
begin_date = end_date - timezone.timedelta(days=last_days)
|
|
|
|
|
|
|
|
# prepare querysets
|
2017-01-17 17:16:53 +01:00
|
|
|
# TODO: retirer les opgroup dont tous les op sont annulées
|
2017-04-02 17:03:20 +02:00
|
|
|
opegroups = OperationGroup.objects.filter(on_acc=account)
|
2018-10-06 12:35:49 +02:00
|
|
|
transfers = Transfer.objects.filter(canceled_at=None).select_related("group")
|
2017-04-11 23:13:54 +02:00
|
|
|
recv_transfers = transfers.filter(to_acc=account)
|
|
|
|
sent_transfers = transfers.filter(from_acc=account)
|
2017-04-02 17:03:20 +02:00
|
|
|
|
|
|
|
# apply filters
|
|
|
|
if begin_date is not None:
|
|
|
|
opegroups = opegroups.filter(at__gte=begin_date)
|
|
|
|
recv_transfers = recv_transfers.filter(group__at__gte=begin_date)
|
|
|
|
sent_transfers = sent_transfers.filter(group__at__gte=begin_date)
|
|
|
|
|
|
|
|
if end_date is not None:
|
|
|
|
opegroups = opegroups.filter(at__lte=end_date)
|
|
|
|
recv_transfers = recv_transfers.filter(group__at__lte=end_date)
|
|
|
|
sent_transfers = sent_transfers.filter(group__at__lte=end_date)
|
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
# On transforme tout ça en une liste de dictionnaires sous la forme
|
|
|
|
# {'at': date,
|
|
|
|
# 'amount': changement de la balance (négatif si diminue la balance,
|
|
|
|
# positif si l'augmente),
|
|
|
|
# 'label': text descriptif,
|
|
|
|
# 'balance': état de la balance après l'action (0 pour le moment,
|
|
|
|
# sera mis à jour lors d'une
|
|
|
|
# autre passe)
|
|
|
|
# }
|
2017-04-02 17:03:20 +02:00
|
|
|
|
2017-04-03 16:07:31 +02:00
|
|
|
actions = []
|
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
actions.append(
|
2017-01-17 17:16:53 +01:00
|
|
|
{
|
2018-10-06 12:35:49 +02:00
|
|
|
"at": (begin_date or account.created_at).isoformat(),
|
|
|
|
"amount": 0,
|
|
|
|
"balance": 0,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
actions.append(
|
|
|
|
{"at": (end_date or timezone.now()).isoformat(), "amount": 0, "balance": 0}
|
|
|
|
)
|
|
|
|
|
|
|
|
actions += (
|
|
|
|
[
|
|
|
|
{"at": ope_grp.at.isoformat(), "amount": ope_grp.amount, "balance": 0}
|
|
|
|
for ope_grp in opegroups
|
|
|
|
]
|
|
|
|
+ [
|
|
|
|
{"at": tr.group.at.isoformat(), "amount": tr.amount, "balance": 0}
|
|
|
|
for tr in recv_transfers
|
|
|
|
]
|
|
|
|
+ [
|
|
|
|
{"at": tr.group.at.isoformat(), "amount": -tr.amount, "balance": 0}
|
|
|
|
for tr in sent_transfers
|
|
|
|
]
|
|
|
|
)
|
2017-01-17 17:16:53 +01:00
|
|
|
# Maintenant on trie la liste des actions par ordre du plus récent
|
|
|
|
# an plus ancien et on met à jour la balance
|
2017-04-03 16:07:31 +02:00
|
|
|
if len(actions) > 1:
|
2018-10-06 12:35:49 +02:00
|
|
|
actions = sorted(actions, key=lambda k: k["at"], reverse=True)
|
|
|
|
actions[0]["balance"] = account.balance
|
|
|
|
for i in range(len(actions) - 1):
|
|
|
|
actions[i + 1]["balance"] = (
|
|
|
|
actions[i]["balance"] - actions[i + 1]["amount"]
|
|
|
|
)
|
2017-01-17 17:16:53 +01:00
|
|
|
return actions
|
|
|
|
|
2017-04-02 17:03:20 +02:00
|
|
|
def get_context_data(self, *args, **kwargs):
|
2017-01-17 17:16:53 +01:00
|
|
|
context = {}
|
2017-04-02 17:03:20 +02:00
|
|
|
|
2020-09-16 17:16:49 +02:00
|
|
|
form = AccountStatForm(self.request.GET)
|
2017-04-02 17:03:20 +02:00
|
|
|
|
2020-09-16 17:16:49 +02:00
|
|
|
if not form.is_valid():
|
|
|
|
raise SuspiciousOperation(
|
|
|
|
"Invalid AccountStatForm. Did someone tamper with the GET parameters ?"
|
|
|
|
)
|
|
|
|
|
|
|
|
changes = self.get_changes_list(**form.cleaned_data)
|
2017-04-02 17:03:20 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
context["charts"] = [
|
|
|
|
{"color": "rgb(200, 20, 60)", "label": "Balance", "values": changes}
|
|
|
|
]
|
|
|
|
context["is_time_chart"] = True
|
2017-04-03 17:06:32 +02:00
|
|
|
if len(changes) > 0:
|
2018-10-06 12:35:49 +02:00
|
|
|
context["min_date"] = changes[-1]["at"]
|
|
|
|
context["max_date"] = changes[0]["at"]
|
2017-01-17 17:16:53 +01:00
|
|
|
# TODO: offset
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2016-12-20 22:46:38 +01:00
|
|
|
# ------------------------
|
2016-12-24 12:33:04 +01:00
|
|
|
# Consommation personnelle
|
2016-12-20 22:46:38 +01:00
|
|
|
# ------------------------
|
|
|
|
|
|
|
|
|
2020-03-09 15:06:55 +01:00
|
|
|
@method_decorator(login_required, name="dispatch")
|
2020-03-09 16:09:12 +01:00
|
|
|
class AccountStatOperationList(UserAccountMixin, SingleResumeStat):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Menu général pour l'historique de consommation d'un compte
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-12-20 22:46:38 +01:00
|
|
|
model = Account
|
2020-03-09 16:15:15 +01:00
|
|
|
slug_url_kwarg = "trigramme"
|
|
|
|
slug_field = "trigramme"
|
2018-10-06 12:35:49 +02:00
|
|
|
url_stat = "kfet.account.stat.operation"
|
2016-12-20 22:46:38 +01:00
|
|
|
|
2020-03-09 16:20:49 +01:00
|
|
|
def get_stats(self):
|
|
|
|
scales_def = [
|
|
|
|
(
|
|
|
|
"Tout le temps",
|
|
|
|
MonthScale,
|
2021-01-21 20:55:23 +01:00
|
|
|
{"last": True, "begin": self.object.created_at.replace(tzinfo=None)},
|
2020-03-09 16:20:49 +01:00
|
|
|
False,
|
|
|
|
),
|
|
|
|
("1 an", MonthScale, {"last": True, "n_steps": 12}, False),
|
|
|
|
("3 mois", WeekScale, {"last": True, "n_steps": 13}, True),
|
|
|
|
("2 semaines", DayScale, {"last": True, "n_steps": 14}, False),
|
|
|
|
]
|
2016-12-20 22:46:38 +01:00
|
|
|
|
2020-09-16 19:19:29 +02:00
|
|
|
return scale_url_params(scales_def)
|
2016-12-24 12:33:04 +01:00
|
|
|
|
2016-12-20 22:46:38 +01:00
|
|
|
|
2020-03-09 15:06:55 +01:00
|
|
|
@method_decorator(login_required, name="dispatch")
|
2020-03-09 16:09:12 +01:00
|
|
|
class AccountStatOperation(UserAccountMixin, ScaleMixin, JSONDetailView):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Statistiques (JSON) de consommation (nb d'items achetés) d'un compte.
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
model = Account
|
2020-03-09 16:15:15 +01:00
|
|
|
slug_url_kwarg = "trigramme"
|
|
|
|
slug_field = "trigramme"
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-04-03 03:12:52 +02:00
|
|
|
def get_context_data(self, *args, **kwargs):
|
2020-03-09 16:11:08 +01:00
|
|
|
context = super().get_context_data(*args, **kwargs)
|
2016-12-20 22:46:38 +01:00
|
|
|
|
2020-09-16 17:16:49 +02:00
|
|
|
operations = (
|
|
|
|
Operation.objects.filter(
|
|
|
|
type=Operation.PURCHASE, group__on_acc=self.object, canceled_at=None
|
|
|
|
)
|
|
|
|
.values("article_nb", "group__at")
|
|
|
|
.order_by("group__at")
|
|
|
|
)
|
2017-01-17 17:16:53 +01:00
|
|
|
# On compte les opérations
|
2020-03-09 16:11:08 +01:00
|
|
|
nb_ventes = self.scale.chunkify_qs(
|
|
|
|
operations, field="group__at", aggregate=Sum("article_nb")
|
|
|
|
)
|
2016-12-20 22:46:38 +01:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
context["charts"] = [
|
|
|
|
{
|
|
|
|
"color": "rgb(200, 20, 60)",
|
|
|
|
"label": "NB items achetés",
|
|
|
|
"values": nb_ventes,
|
|
|
|
}
|
|
|
|
]
|
2017-01-17 17:16:53 +01:00
|
|
|
return context
|
2016-12-20 22:46:38 +01:00
|
|
|
|
|
|
|
|
2016-12-10 17:33:24 +01:00
|
|
|
# ------------------------
|
2020-09-16 17:16:49 +02:00
|
|
|
# Article Statistiques Last
|
2016-12-10 17:33:24 +01:00
|
|
|
# ------------------------
|
|
|
|
|
|
|
|
|
2020-03-09 16:09:12 +01:00
|
|
|
@method_decorator(teamkfet_required, name="dispatch")
|
2017-04-03 03:12:52 +02:00
|
|
|
class ArticleStatSalesList(SingleResumeStat):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Menu pour les statistiques de vente d'un article.
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2016-12-10 17:33:24 +01:00
|
|
|
model = Article
|
2016-12-20 22:46:38 +01:00
|
|
|
nb_default = 2
|
2018-10-06 12:35:49 +02:00
|
|
|
url_stat = "kfet.article.stat.sales"
|
2016-12-09 21:45:34 +01:00
|
|
|
|
2020-03-09 16:20:49 +01:00
|
|
|
def get_stats(self):
|
|
|
|
first_conso = (
|
|
|
|
Operation.objects.filter(article=self.object)
|
|
|
|
.order_by("group__at")
|
2020-03-09 16:32:38 +01:00
|
|
|
.values_list("group__at", flat=True)
|
2020-03-09 16:20:49 +01:00
|
|
|
.first()
|
|
|
|
)
|
2020-03-09 16:32:38 +01:00
|
|
|
if first_conso is None:
|
|
|
|
# On le crée dans le passé au cas où
|
|
|
|
first_conso = timezone.now() - timedelta(seconds=1)
|
2020-03-09 16:20:49 +01:00
|
|
|
scales_def = [
|
2020-09-16 19:19:29 +02:00
|
|
|
(
|
|
|
|
"Tout le temps",
|
|
|
|
MonthScale,
|
|
|
|
{"last": True, "begin": first_conso.strftime("%Y-%m-%d %H:%M:%S")},
|
|
|
|
False,
|
|
|
|
),
|
2020-03-09 16:20:49 +01:00
|
|
|
("1 an", MonthScale, {"last": True, "n_steps": 12}, False),
|
|
|
|
("3 mois", WeekScale, {"last": True, "n_steps": 13}, True),
|
|
|
|
("2 semaines", DayScale, {"last": True, "n_steps": 14}, False),
|
|
|
|
]
|
|
|
|
|
|
|
|
return scale_url_params(scales_def)
|
2016-12-24 12:33:04 +01:00
|
|
|
|
2016-12-09 21:45:34 +01:00
|
|
|
|
2020-03-09 16:09:12 +01:00
|
|
|
@method_decorator(teamkfet_required, name="dispatch")
|
2017-04-03 03:12:52 +02:00
|
|
|
class ArticleStatSales(ScaleMixin, JSONDetailView):
|
2020-03-09 15:06:55 +01:00
|
|
|
"""
|
|
|
|
Statistiques (JSON) de vente d'un article.
|
|
|
|
Sépare LIQ et les comptes K-Fêt, et rajoute le total.
|
|
|
|
"""
|
2018-10-06 12:35:49 +02:00
|
|
|
|
2017-01-17 17:16:53 +01:00
|
|
|
model = Article
|
2018-10-06 12:35:49 +02:00
|
|
|
context_object_name = "article"
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-04-03 03:12:52 +02:00
|
|
|
def get_context_data(self, *args, **kwargs):
|
2020-03-09 16:15:15 +01:00
|
|
|
context = super().get_context_data(*args, **kwargs)
|
2017-04-03 03:12:52 +02:00
|
|
|
scale = self.scale
|
2017-01-17 17:16:53 +01:00
|
|
|
|
2017-04-12 18:03:31 +02:00
|
|
|
all_purchases = (
|
2018-10-06 12:35:49 +02:00
|
|
|
Operation.objects.filter(
|
|
|
|
type=Operation.PURCHASE, article=self.object, canceled_at=None
|
2017-04-12 18:03:31 +02:00
|
|
|
)
|
2018-10-06 12:35:49 +02:00
|
|
|
.values("group__at", "article_nb")
|
|
|
|
.order_by("group__at")
|
2017-04-03 03:12:52 +02:00
|
|
|
)
|
2020-09-16 19:19:29 +02:00
|
|
|
cof_accts = all_purchases.filter(group__on_acc__cofprofile__is_cof=True)
|
|
|
|
noncof_accts = all_purchases.exclude(group__on_acc__cofprofile__is_cof=True)
|
2017-04-12 18:03:31 +02:00
|
|
|
|
2020-09-16 17:16:49 +02:00
|
|
|
nb_cof = scale.chunkify_qs(
|
|
|
|
cof_accts, field="group__at", aggregate=Sum("article_nb")
|
2017-04-03 03:12:52 +02:00
|
|
|
)
|
2020-09-16 17:16:49 +02:00
|
|
|
nb_noncof = scale.chunkify_qs(
|
|
|
|
noncof_accts, field="group__at", aggregate=Sum("article_nb")
|
2017-04-12 18:03:31 +02:00
|
|
|
)
|
2020-09-16 17:16:49 +02:00
|
|
|
nb_ventes = [n1 + n2 for n1, n2 in zip(nb_cof, nb_noncof)]
|
2017-04-12 18:03:31 +02:00
|
|
|
|
2018-10-06 12:35:49 +02:00
|
|
|
context["charts"] = [
|
|
|
|
{
|
|
|
|
"color": "rgb(200, 20, 60)",
|
|
|
|
"label": "Toutes consommations",
|
|
|
|
"values": nb_ventes,
|
|
|
|
},
|
2022-01-05 10:45:32 +01:00
|
|
|
{"color": "rgb(54, 162, 235)", "label": "Comptes K-Fêt", "values": nb_cof},
|
2018-10-06 12:35:49 +02:00
|
|
|
{
|
|
|
|
"color": "rgb(255, 205, 86)",
|
2022-01-05 10:45:32 +01:00
|
|
|
"label": "LIQ",
|
2020-09-16 17:16:49 +02:00
|
|
|
"values": nb_noncof,
|
2018-10-06 12:35:49 +02:00
|
|
|
},
|
|
|
|
]
|
2017-01-17 17:16:53 +01:00
|
|
|
return context
|
2020-07-04 13:50:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ---
|
|
|
|
# Autocompletion views
|
|
|
|
# ---
|
|
|
|
|
|
|
|
|
2020-07-01 22:29:07 +02:00
|
|
|
class AccountCreateAutocompleteView(PermissionRequiredMixin, AutocompleteView):
|
2020-09-07 11:32:28 +02:00
|
|
|
template_name = "kfet/search_results.html"
|
2020-07-04 13:50:19 +02:00
|
|
|
permission_required = "kfet.is_team"
|
2020-07-18 16:05:16 +02:00
|
|
|
search_composer = kfet_autocomplete
|
2020-07-04 13:50:19 +02:00
|
|
|
|
|
|
|
|
2020-07-01 22:29:07 +02:00
|
|
|
class AccountSearchAutocompleteView(PermissionRequiredMixin, AutocompleteView):
|
2020-07-04 13:50:19 +02:00
|
|
|
permission_required = "kfet.is_team"
|
2020-07-18 16:05:16 +02:00
|
|
|
search_composer = kfet_account_only_autocomplete
|