gestioCOF/kfet/forms.py
Morpheus 183f655828 Update kfet/forms.py
Correction de l'assignation des promos
2024-11-10 11:48:43 +01:00

697 lines
21 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 gettext_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",)
class ContactForm(forms.Form):
from_email = forms.EmailField(
label="Adresse mail",
help_text="Si aucune adresse mail n'est renseignée, la soumission sera anonyme.",
required=False,
)
subject = forms.CharField(label="Objet", required=True)
message = forms.CharField(widget=forms.Textarea, required=True)
def clean_from_email(self):
return self.cleaned_data["from_email"] or "Anonyme <k-fet@ens.psl.eu>"
class DemandeSoireeForm(forms.Form):
HORAIRE_CHOICES = map(lambda s: (s, s), ("22h", "23h", "00h", "01h", "02h", "03h"))
SERVICE_CHOICES = (
("K-Fêt", "K-Fêt standard (L'équipe K-Fêt fait le service normal au bar)"),
("Kalô", "Type Kalô (Vous ramenez vos propres boissons et servez vous-mêmes)"),
)
nom = forms.CharField()
from_email = forms.EmailField(label="Adresse mail de contact")
contact_boum = forms.BooleanField(label="Contacter le Boum", required=False)
contact_pls = forms.BooleanField(label="Contacter PLS", required=False)
theme = forms.CharField(label="Thème de la soirée")
horaire_fin = forms.ChoiceField(label="Horaire de fin", choices=HORAIRE_CHOICES)
service = forms.ChoiceField(label="Mode de service", choices=SERVICE_CHOICES)
date = forms.CharField(label="Date souhaitée")
respo1 = forms.CharField(label="Nom de la personne respo n°1")
respo2 = forms.CharField(label="Nom de la personne respo n°2")
respo3 = forms.CharField(label="Nom de la personne respo n°3")
respo4 = forms.CharField(label="Nom de la personne respo n°4")
remarques = forms.CharField(
label="Remarques supplémentaires", widget=forms.Textarea
)
# -----
# Account forms
# -----
def default_promo():
now = date.today()
return now.month <= 7 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)