657 lines
20 KiB
Python
657 lines
20 KiB
Python
from datetime import date, timedelta
|
|
from decimal import Decimal
|
|
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.auth.models import User
|
|
from django.core import validators
|
|
from django.core.exceptions import ValidationError
|
|
from django.forms import modelformset_factory
|
|
from django.utils import timezone
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from djconfig.forms import ConfigForm
|
|
|
|
from gestioncof.models import CofProfile
|
|
from kfet.models import (
|
|
Account,
|
|
Article,
|
|
ArticleCategory,
|
|
Checkout,
|
|
CheckoutStatement,
|
|
Operation,
|
|
OperationGroup,
|
|
Supplier,
|
|
Transfer,
|
|
TransferGroup,
|
|
)
|
|
from kfet.statistic import SCALE_CLASS_CHOICES
|
|
|
|
from . import KFET_DELETED_TRIGRAMME
|
|
from .auth import KFET_GENERIC_TRIGRAMME
|
|
from .auth.forms import UserGroupForm # noqa
|
|
|
|
# -----
|
|
# Widgets
|
|
# -----
|
|
|
|
|
|
class DateTimeWidget(forms.DateTimeInput):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.attrs["format"] = "%Y-%m-%d %H:%M"
|
|
|
|
class Media:
|
|
css = {"all": ("kfet/vendor/bootstrap/bootstrap-datetimepicker.min.css",)}
|
|
js = ("kfet/vendor/bootstrap/bootstrap-datetimepicker.min.js",)
|
|
|
|
|
|
# -----
|
|
# Account forms
|
|
# -----
|
|
|
|
|
|
def default_promo():
|
|
now = date.today()
|
|
return now.month <= 8 and now.year - 1 or now.year
|
|
|
|
|
|
def get_promo_choices():
|
|
return [("", "Sans promo")] + [(r, r) for r in range(1980, date.today().year + 1)]
|
|
|
|
|
|
class AccountForm(forms.ModelForm):
|
|
promo = forms.TypedChoiceField(
|
|
choices=get_promo_choices,
|
|
coerce=int,
|
|
empty_value=None,
|
|
initial=default_promo,
|
|
required=False,
|
|
)
|
|
|
|
# Surcharge pour passer data à Account.save()
|
|
def save(self, data={}, *args, **kwargs):
|
|
obj = super().save(commit=False, *args, **kwargs)
|
|
obj.save(data=data)
|
|
return obj
|
|
|
|
class Meta:
|
|
model = Account
|
|
fields = ["trigramme", "promo", "nickname"]
|
|
widgets = {"trigramme": forms.TextInput(attrs={"autocomplete": "off"})}
|
|
|
|
|
|
class AccountBalanceForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Account
|
|
fields = ["balance"]
|
|
|
|
|
|
class AccountTriForm(AccountForm):
|
|
def clean_trigramme(self):
|
|
trigramme = self.cleaned_data["trigramme"]
|
|
return trigramme.upper()
|
|
|
|
class Meta(AccountForm.Meta):
|
|
fields = ["trigramme"]
|
|
|
|
|
|
class AccountNoTriForm(AccountForm):
|
|
class Meta(AccountForm.Meta):
|
|
exclude = ["trigramme"]
|
|
|
|
|
|
class AccountPwdForm(forms.Form):
|
|
pwd1 = forms.CharField(
|
|
label="Mot de passe K-Fêt",
|
|
required=False,
|
|
help_text="Le mot de passe doit contenir au moins huit caractères",
|
|
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
|
min_length=8,
|
|
)
|
|
pwd2 = forms.CharField(
|
|
label="Confirmer le mot de passe",
|
|
required=False,
|
|
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
|
)
|
|
|
|
def __init__(self, *args, account=None, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.account = account
|
|
|
|
def clean(self):
|
|
pwd1 = self.cleaned_data.get("pwd1", "")
|
|
pwd2 = self.cleaned_data.get("pwd2", "")
|
|
if pwd1 != pwd2:
|
|
self.add_error("pwd2", "Les mots de passe doivent être identiques !")
|
|
super().clean()
|
|
|
|
def save(self, commit=True):
|
|
password = self.cleaned_data["pwd1"]
|
|
self.account.change_pwd(password)
|
|
if commit:
|
|
self.account.save()
|
|
|
|
return self.account
|
|
|
|
|
|
class AccountFrozenForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Account
|
|
fields = ["is_frozen"]
|
|
|
|
|
|
class CofForm(forms.ModelForm):
|
|
def clean_is_cof(self):
|
|
instance = getattr(self, "instance", None)
|
|
if instance and instance.pk:
|
|
return instance.is_cof
|
|
else:
|
|
return False
|
|
|
|
class Meta:
|
|
model = CofProfile
|
|
fields = ["login_clipper", "is_cof", "departement"]
|
|
|
|
|
|
class UserForm(forms.ModelForm):
|
|
class Meta:
|
|
model = User
|
|
fields = ["username", "first_name", "last_name", "email"]
|
|
help_texts = {"username": ""}
|
|
|
|
|
|
class UserRestrictForm(UserForm):
|
|
class Meta(UserForm.Meta):
|
|
fields = ["first_name", "last_name"]
|
|
|
|
|
|
class UserInfoForm(UserForm):
|
|
first_name = forms.CharField(label="Prénom", disabled=True)
|
|
last_name = forms.CharField(label="Nom de famille", disabled=True)
|
|
|
|
class Meta(UserForm.Meta):
|
|
fields = ["first_name", "last_name"]
|
|
|
|
|
|
# -----
|
|
# Checkout forms
|
|
# -----
|
|
|
|
|
|
class CheckoutForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Checkout
|
|
fields = ["name", "valid_from", "valid_to", "balance", "is_protected"]
|
|
widgets = {"valid_from": DateTimeWidget(), "valid_to": DateTimeWidget()}
|
|
|
|
|
|
class CheckoutRestrictForm(CheckoutForm):
|
|
class Meta(CheckoutForm.Meta):
|
|
fields = ["name", "valid_from", "valid_to"]
|
|
|
|
|
|
class CheckoutStatementCreateForm(forms.ModelForm):
|
|
balance_001 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_002 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_005 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_01 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_02 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_05 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_1 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_2 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_5 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_10 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_20 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_50 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_100 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_200 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
balance_500 = forms.IntegerField(min_value=0, initial=0, required=False)
|
|
|
|
class Meta:
|
|
model = CheckoutStatement
|
|
exclude = [
|
|
"by",
|
|
"at",
|
|
"checkout",
|
|
"amount_error",
|
|
"amount_taken",
|
|
"balance_old",
|
|
"balance_new",
|
|
]
|
|
|
|
def clean(self):
|
|
not_count = self.cleaned_data["not_count"]
|
|
if not not_count and (
|
|
self.cleaned_data["balance_001"] is None
|
|
or self.cleaned_data["balance_002"] is None
|
|
or self.cleaned_data["balance_005"] is None
|
|
or self.cleaned_data["balance_01"] is None
|
|
or self.cleaned_data["balance_02"] is None
|
|
or self.cleaned_data["balance_05"] is None
|
|
or self.cleaned_data["balance_1"] is None
|
|
or self.cleaned_data["balance_2"] is None
|
|
or self.cleaned_data["balance_5"] is None
|
|
or self.cleaned_data["balance_10"] is None
|
|
or self.cleaned_data["balance_20"] is None
|
|
or self.cleaned_data["balance_50"] is None
|
|
or self.cleaned_data["balance_100"] is None
|
|
or self.cleaned_data["balance_200"] is None
|
|
or self.cleaned_data["balance_500"] is None
|
|
):
|
|
raise ValidationError(
|
|
"Y'a un problème. Si tu comptes la caisse, mets au moins des 0 stp."
|
|
)
|
|
super().clean()
|
|
|
|
|
|
class CheckoutStatementUpdateForm(forms.ModelForm):
|
|
class Meta:
|
|
model = CheckoutStatement
|
|
exclude = ["by", "at", "checkout", "amount_error", "amount_taken"]
|
|
|
|
|
|
# -----
|
|
# Category
|
|
# -----
|
|
|
|
|
|
class CategoryForm(forms.ModelForm):
|
|
class Meta:
|
|
model = ArticleCategory
|
|
fields = ["name", "has_addcost", "has_reduction"]
|
|
|
|
|
|
# -----
|
|
# Article forms
|
|
# -----
|
|
|
|
|
|
class ArticleForm(forms.ModelForm):
|
|
category_new = forms.CharField(
|
|
label="Créer une catégorie", max_length=45, required=False
|
|
)
|
|
category = forms.ModelChoiceField(
|
|
label="Catégorie", queryset=ArticleCategory.objects.all(), required=False
|
|
)
|
|
|
|
suppliers = forms.ModelMultipleChoiceField(
|
|
label="Fournisseurs", queryset=Supplier.objects.all(), required=False
|
|
)
|
|
supplier_new = forms.CharField(
|
|
label="Créer un fournisseur", max_length=45, required=False
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if self.instance.pk:
|
|
self.initial["suppliers"] = self.instance.suppliers.values_list(
|
|
"pk", flat=True
|
|
)
|
|
|
|
def clean(self):
|
|
category = self.cleaned_data.get("category")
|
|
category_new = self.cleaned_data.get("category_new")
|
|
|
|
if not category and not category_new:
|
|
raise ValidationError("Sélectionnez une catégorie ou créez en une")
|
|
elif not category:
|
|
category, _ = ArticleCategory.objects.get_or_create(name=category_new)
|
|
self.cleaned_data["category"] = category
|
|
|
|
super().clean()
|
|
|
|
class Meta:
|
|
model = Article
|
|
fields = [
|
|
"name",
|
|
"is_sold",
|
|
"hidden",
|
|
"price",
|
|
"stock",
|
|
"category",
|
|
"box_type",
|
|
"box_capacity",
|
|
]
|
|
|
|
|
|
class ArticleRestrictForm(ArticleForm):
|
|
class Meta(ArticleForm.Meta):
|
|
fields = [
|
|
"name",
|
|
"is_sold",
|
|
"hidden",
|
|
"price",
|
|
"category",
|
|
"box_type",
|
|
"box_capacity",
|
|
]
|
|
|
|
|
|
# -----
|
|
# K-Psul forms
|
|
# -----
|
|
|
|
|
|
class KPsulOperationGroupForm(forms.ModelForm):
|
|
# FIXME(AD): Use timezone.now instead of timezone.now() to avoid using a
|
|
# fixed datetime (application boot here).
|
|
# One may even use: Checkout.objects.is_valid() if changing
|
|
# to now = timezone.now is ok in 'is_valid' definition.
|
|
checkout = forms.ModelChoiceField(
|
|
queryset=Checkout.objects.filter(
|
|
is_protected=False,
|
|
valid_from__lte=timezone.now(),
|
|
valid_to__gte=timezone.now(),
|
|
),
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
on_acc = forms.ModelChoiceField(
|
|
queryset=Account.objects.exclude(
|
|
trigramme__in=[KFET_DELETED_TRIGRAMME, KFET_GENERIC_TRIGRAMME]
|
|
),
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
|
|
class Meta:
|
|
model = OperationGroup
|
|
fields = ["on_acc", "checkout", "comment"]
|
|
|
|
|
|
class KPsulAccountForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Account
|
|
fields = ["trigramme"]
|
|
widgets = {
|
|
"trigramme": forms.TextInput(
|
|
attrs={"autocomplete": "off", "spellcheck": "false"}
|
|
)
|
|
}
|
|
|
|
|
|
class KPsulCheckoutForm(forms.Form):
|
|
checkout = forms.ModelChoiceField(
|
|
queryset=None, widget=forms.Select(attrs={"id": "id_checkout_select"})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Create the queryset on form instanciation to use the current time.
|
|
self.fields["checkout"].queryset = Checkout.objects.is_valid().filter(
|
|
is_protected=False
|
|
)
|
|
|
|
|
|
class KPsulOperationForm(forms.ModelForm):
|
|
article = forms.ModelChoiceField(
|
|
queryset=Article.objects.select_related("category").all(),
|
|
required=False,
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
article_nb = forms.IntegerField(
|
|
required=False,
|
|
initial=None,
|
|
validators=[validators.MinValueValidator(1)],
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
|
|
class Meta:
|
|
model = Operation
|
|
fields = ["type", "amount", "article", "article_nb"]
|
|
widgets = {"type": forms.HiddenInput(), "amount": forms.HiddenInput()}
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
type_ope = self.cleaned_data.get("type")
|
|
amount = self.cleaned_data.get("amount")
|
|
article = self.cleaned_data.get("article")
|
|
article_nb = self.cleaned_data.get("article_nb")
|
|
errors = []
|
|
if type_ope and type_ope == Operation.PURCHASE:
|
|
if not article or article_nb is None or article_nb < 1:
|
|
errors.append(
|
|
ValidationError("Un achat nécessite un article et une quantité")
|
|
)
|
|
elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
|
|
if not amount or article or article_nb:
|
|
errors.append(ValidationError("Bad request"))
|
|
else:
|
|
if type_ope == Operation.DEPOSIT and amount <= 0:
|
|
errors.append(ValidationError("Charge non positive"))
|
|
elif type_ope == Operation.WITHDRAW and amount >= 0:
|
|
errors.append(ValidationError("Retrait non négatif"))
|
|
self.cleaned_data["article"] = None
|
|
self.cleaned_data["article_nb"] = None
|
|
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
|
|
|
|
KPsulOperationFormSet = modelformset_factory(
|
|
Operation,
|
|
form=KPsulOperationForm,
|
|
can_delete=True,
|
|
extra=0,
|
|
min_num=1,
|
|
validate_min=True,
|
|
)
|
|
|
|
|
|
class AddcostForm(forms.Form):
|
|
trigramme = forms.CharField(required=False)
|
|
amount = forms.DecimalField(
|
|
required=False, max_digits=6, decimal_places=2, min_value=Decimal(0)
|
|
)
|
|
|
|
def clean(self):
|
|
trigramme = self.cleaned_data.get("trigramme")
|
|
if trigramme:
|
|
try:
|
|
Account.objects.get(trigramme=trigramme)
|
|
except Account.DoesNotExist:
|
|
raise ValidationError("Compte invalide")
|
|
else:
|
|
self.cleaned_data["amount"] = 0
|
|
super().clean()
|
|
|
|
|
|
# -----
|
|
# Settings forms
|
|
# -----
|
|
|
|
|
|
class KFetConfigForm(ConfigForm):
|
|
|
|
kfet_reduction_cof = forms.DecimalField(
|
|
label="Réduction COF",
|
|
initial=Decimal("20"),
|
|
max_digits=6,
|
|
decimal_places=2,
|
|
help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
|
|
"achat par un-e membre du COF sur le montant en euros.",
|
|
)
|
|
kfet_addcost_amount = forms.DecimalField(
|
|
label="Montant de la majoration (en €)",
|
|
initial=Decimal("0"),
|
|
required=False,
|
|
max_digits=6,
|
|
decimal_places=2,
|
|
)
|
|
kfet_addcost_for = forms.ModelChoiceField(
|
|
label="Destinataire de la majoration",
|
|
initial=None,
|
|
required=False,
|
|
help_text="Laissez vide pour désactiver la majoration.",
|
|
queryset=(
|
|
Account.objects.select_related("cofprofile", "cofprofile__user").all()
|
|
),
|
|
)
|
|
kfet_overdraft_duration = forms.DurationField(
|
|
label="Durée du découvert autorisé par défaut", initial=timedelta(days=1)
|
|
)
|
|
kfet_overdraft_amount = forms.DecimalField(
|
|
label="Montant du découvert autorisé par défaut (en €)",
|
|
initial=Decimal("20"),
|
|
max_digits=6,
|
|
decimal_places=2,
|
|
)
|
|
kfet_cancel_duration = forms.DurationField(
|
|
label="Durée pour annuler une commande sans mot de passe",
|
|
initial=timedelta(minutes=5),
|
|
)
|
|
|
|
|
|
class FilterHistoryForm(forms.Form):
|
|
start = forms.DateTimeField(
|
|
label=_("De"),
|
|
widget=DateTimeWidget,
|
|
required=False,
|
|
help_text="Limité à {} jours ({} pour les chefs/trez)".format(
|
|
settings.KFET_HISTORY_DATE_LIMIT.days,
|
|
settings.KFET_HISTORY_LONG_DATE_LIMIT.days,
|
|
),
|
|
)
|
|
end = forms.DateTimeField(label=_("À"), widget=DateTimeWidget, required=False)
|
|
checkout = forms.ModelChoiceField(
|
|
label=_("Caisse"),
|
|
queryset=Checkout.objects.all(),
|
|
required=False,
|
|
empty_label=_("Toutes les caisses"),
|
|
)
|
|
account = forms.ModelChoiceField(
|
|
label=_("Compte"),
|
|
queryset=Account.objects.all(),
|
|
required=False,
|
|
empty_label=_("Tous les comptes"),
|
|
)
|
|
|
|
transfers_only = forms.BooleanField(widget=forms.HiddenInput, required=False)
|
|
opes_only = forms.BooleanField(widget=forms.HiddenInput, required=False)
|
|
|
|
|
|
# -----
|
|
# Transfer forms
|
|
# -----
|
|
|
|
|
|
class TransferGroupForm(forms.ModelForm):
|
|
class Meta:
|
|
model = TransferGroup
|
|
fields = ["comment"]
|
|
|
|
|
|
class TransferForm(forms.ModelForm):
|
|
from_acc = forms.ModelChoiceField(
|
|
queryset=Account.objects.exclude(trigramme__in=["LIQ", "#13", "GNR"]),
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
to_acc = forms.ModelChoiceField(
|
|
queryset=Account.objects.exclude(trigramme__in=["LIQ", "#13", "GNR"]),
|
|
widget=forms.HiddenInput(),
|
|
)
|
|
|
|
def clean_amount(self):
|
|
amount = self.cleaned_data["amount"]
|
|
if amount <= 0:
|
|
raise forms.ValidationError("Le montant d'un transfert doit être positif")
|
|
return amount
|
|
|
|
class Meta:
|
|
model = Transfer
|
|
fields = ["from_acc", "to_acc", "amount"]
|
|
|
|
|
|
TransferFormSet = modelformset_factory(
|
|
Transfer, form=TransferForm, min_num=1, validate_min=True, extra=9
|
|
)
|
|
|
|
# -----
|
|
# Inventory forms
|
|
# -----
|
|
|
|
|
|
class InventoryArticleForm(forms.Form):
|
|
article = forms.ModelChoiceField(
|
|
queryset=Article.objects.all(), widget=forms.HiddenInput()
|
|
)
|
|
stock_new = forms.IntegerField(required=False)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if "initial" in kwargs:
|
|
self.name = kwargs["initial"]["name"]
|
|
self.stock_old = kwargs["initial"]["stock_old"]
|
|
self.category = kwargs["initial"]["category"]
|
|
self.category_name = kwargs["initial"]["category__name"]
|
|
self.box_capacity = kwargs["initial"]["box_capacity"]
|
|
self.is_sold = kwargs["initial"]["is_sold"]
|
|
|
|
|
|
# -----
|
|
# Order forms
|
|
# -----
|
|
|
|
|
|
class OrderArticleForm(forms.Form):
|
|
article = forms.ModelChoiceField(
|
|
queryset=Article.objects.all(), widget=forms.HiddenInput()
|
|
)
|
|
quantity_ordered = forms.IntegerField(required=False)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if "initial" in kwargs:
|
|
self.name = kwargs["initial"]["name"]
|
|
self.stock = kwargs["initial"]["stock"]
|
|
self.category = kwargs["initial"]["category"]
|
|
self.category_name = kwargs["initial"]["category__name"]
|
|
self.box_capacity = kwargs["initial"]["box_capacity"]
|
|
self.v_all = kwargs["initial"]["v_all"]
|
|
self.v_moy = kwargs["initial"]["v_moy"]
|
|
self.v_et = kwargs["initial"]["v_et"]
|
|
self.v_prev = kwargs["initial"]["v_prev"]
|
|
self.c_rec = kwargs["initial"]["c_rec"]
|
|
self.is_sold = kwargs["initial"]["is_sold"]
|
|
|
|
|
|
class OrderArticleToInventoryForm(forms.Form):
|
|
article = forms.ModelChoiceField(
|
|
queryset=Article.objects.all(), widget=forms.HiddenInput()
|
|
)
|
|
price_HT = forms.DecimalField(max_digits=7, decimal_places=4, required=False)
|
|
TVA = forms.DecimalField(max_digits=7, decimal_places=2, required=False)
|
|
rights = forms.DecimalField(max_digits=7, decimal_places=4, required=False)
|
|
quantity_received = forms.IntegerField()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if "initial" in kwargs:
|
|
self.name = kwargs["initial"]["name"]
|
|
self.category = kwargs["initial"]["category"]
|
|
self.category_name = kwargs["initial"]["category__name"]
|
|
self.quantity_ordered = kwargs["initial"]["quantity_ordered"]
|
|
|
|
|
|
# ----
|
|
# Formulaires pour les statistiques K-Fêt
|
|
# ----
|
|
|
|
|
|
class StatScaleForm(forms.Form):
|
|
"""Formulaire pour nettoyer les paramètres envoyés aux
|
|
vues de statistiques K-Fêt. Non destiné à être affiché.
|
|
"""
|
|
|
|
name = forms.ChoiceField(choices=SCALE_CLASS_CHOICES)
|
|
begin = forms.DateTimeField(required=False)
|
|
end = forms.DateTimeField(required=False)
|
|
n_steps = forms.IntegerField(required=False)
|
|
last = forms.BooleanField(required=False)
|
|
|
|
|
|
class AccountStatForm(forms.Form):
|
|
"""Idem, mais pour la balance d'un compte"""
|
|
|
|
begin_date = forms.DateTimeField(required=False)
|
|
end_date = forms.DateTimeField(required=False)
|
|
last_days = forms.IntegerField(required=False)
|