from datetime import timedelta from decimal import Decimal from django import forms 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, AccountNegative, 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 # ----- class AccountForm(forms.ModelForm): # 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", "is_frozen"] 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 AccountRestrictForm(AccountForm): class Meta(AccountForm.Meta): fields = ["is_frozen"] 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, ) pwd2 = forms.CharField( label="Confirmer le mot de passe", required=False, widget=forms.PasswordInput ) def clean(self): pwd1 = self.cleaned_data.get("pwd1", "") pwd2 = self.cleaned_data.get("pwd2", "") if len(pwd1) < 8: raise ValidationError("Mot de passe trop court") if pwd1 != pwd2: raise ValidationError("Les mots de passes sont différents") super().clean() 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"] class AccountNegativeForm(forms.ModelForm): class Meta: model = AccountNegative fields = [ "authz_overdraft_amount", "authz_overdraft_until", "balance_offset", "comment", ] widgets = {"authz_overdraft_until": DateTimeWidget()} # ----- # 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) 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("Montant invalide") 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)