c228416809
- kfet_config gives "reduction_cof" as editable instead of "subvention_cof" - this last one can still be accessed via kfet_config (computed from new "reduction_cof" - add units to numeric values of kfet_config form
552 lines
19 KiB
Python
552 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from datetime import timedelta
|
|
from decimal import Decimal
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.validators import MinLengthValidator
|
|
from django.contrib.auth.models import User, Group, Permission
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.forms import modelformset_factory
|
|
from django.utils import timezone
|
|
|
|
from djconfig.forms import ConfigForm
|
|
|
|
from kfet.models import (
|
|
Account, Checkout, Article, OperationGroup, Operation,
|
|
CheckoutStatement, ArticleCategory, AccountNegative, Transfer,
|
|
TransferGroup, Supplier)
|
|
from gestioncof.models import CofProfile
|
|
|
|
|
|
# -----
|
|
# Widgets
|
|
# -----
|
|
|
|
class DateTimeWidget(forms.DateTimeInput):
|
|
def __init__(self, attrs = None):
|
|
super(DateTimeWidget, self).__init__(attrs)
|
|
self.attrs['format'] = '%Y-%m-%d %H:%M'
|
|
class Media:
|
|
css = {
|
|
'all': ('kfet/css/bootstrap-datetimepicker.min.css',)
|
|
}
|
|
js = (
|
|
'kfet/js/moment.js',
|
|
'kfet/js/moment-fr.js',
|
|
'kfet/js/bootstrap-datetimepicker.min.js',
|
|
)
|
|
# -----
|
|
# Account forms
|
|
# -----
|
|
|
|
class AccountForm(forms.ModelForm):
|
|
|
|
# Surcharge pour passer data à Account.save()
|
|
def save(self, data = {}, *args, **kwargs):
|
|
obj = super(AccountForm, self).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",
|
|
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",
|
|
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(AccountPwdForm, self).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 CofRestrictForm(CofForm):
|
|
class Meta(CofForm.Meta):
|
|
fields = ['departement']
|
|
|
|
class UserForm(forms.ModelForm):
|
|
def __init__(self, *args, **kwargs):
|
|
from_clipper = kwargs.pop('from_clipper', False)
|
|
new_user = kwargs.get('instance') is None and not from_clipper
|
|
super(UserForm, self).__init__(*args, **kwargs)
|
|
if new_user:
|
|
self.fields['username'].validators = [MinLengthValidator(9)]
|
|
|
|
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 UserRestrictTeamForm(UserForm):
|
|
class Meta(UserForm.Meta):
|
|
fields = ['first_name', 'last_name', 'email']
|
|
|
|
class UserGroupForm(forms.ModelForm):
|
|
groups = forms.ModelMultipleChoiceField(
|
|
Group.objects.filter(name__icontains='K-Fêt'),
|
|
label='Statut équipe',
|
|
required=False)
|
|
|
|
def clean_groups(self):
|
|
groups = self.cleaned_data.get('groups')
|
|
# Si aucun groupe, on le dénomme
|
|
if not groups:
|
|
groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
|
return groups
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = ['groups']
|
|
|
|
class GroupForm(forms.ModelForm):
|
|
permissions = forms.ModelMultipleChoiceField(
|
|
queryset= Permission.objects.filter(content_type__in=
|
|
ContentType.objects.filter(app_label='kfet')))
|
|
|
|
def clean_name(self):
|
|
name = self.cleaned_data['name']
|
|
return 'K-Fêt %s' % name
|
|
|
|
class Meta:
|
|
model = Group
|
|
fields = ['name', 'permissions']
|
|
|
|
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 (et t'as pas idée de comment c'est long de vérifier que t'as mis des valeurs de partout...)")
|
|
super(CheckoutStatementCreateForm, self).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']
|
|
|
|
# -----
|
|
# 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(ArticleForm, self).__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(ArticleForm, self).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):
|
|
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='GNR'),
|
|
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=Checkout.objects.filter(
|
|
is_protected=False, valid_from__lte=timezone.now(),
|
|
valid_to__gte=timezone.now()),
|
|
widget=forms.Select(attrs={'id':'id_checkout_select'}))
|
|
|
|
class KPsulOperationForm(forms.ModelForm):
|
|
article = forms.ModelChoiceField(
|
|
queryset=Article.objects.select_related('category').all(),
|
|
required=False,
|
|
widget = forms.HiddenInput())
|
|
class Meta:
|
|
model = Operation
|
|
fields = ['type', 'amount', 'article', 'article_nb']
|
|
widgets = {
|
|
'type': forms.HiddenInput(),
|
|
'amount': forms.HiddenInput(),
|
|
'article_nb': forms.HiddenInput(),
|
|
}
|
|
|
|
def clean(self):
|
|
super(KPsulOperationForm, self).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')
|
|
if type_ope and type_ope == Operation.PURCHASE:
|
|
if not article or not article_nb:
|
|
raise ValidationError(
|
|
"Un achat nécessite un article et une quantité")
|
|
if article_nb < 1:
|
|
raise ValidationError("Impossible d'acheter moins de 1 article")
|
|
elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
|
|
if not amount or article or article_nb:
|
|
raise ValidationError("Bad request")
|
|
if type_ope == Operation.DEPOSIT and amount <= 0:
|
|
raise ValidationError("Charge non positive")
|
|
if type_ope == Operation.WITHDRAW and amount >= 0:
|
|
raise ValidationError("Retrait non négatif")
|
|
self.cleaned_data['article'] = None
|
|
self.cleaned_data['article_nb'] = None
|
|
|
|
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(AddcostForm, self).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):
|
|
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())
|
|
accounts = forms.ModelMultipleChoiceField(queryset = Account.objects.all())
|
|
|
|
# -----
|
|
# 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(InventoryArticleForm, self).__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']
|
|
|
|
# -----
|
|
# 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(OrderArticleForm, self).__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_s1 = kwargs['initial']['v_s1']
|
|
self.v_s2 = kwargs['initial']['v_s2']
|
|
self.v_s3 = kwargs['initial']['v_s3']
|
|
self.v_s4 = kwargs['initial']['v_s4']
|
|
self.v_s5 = kwargs['initial']['v_s5']
|
|
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']
|
|
|
|
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(OrderArticleToInventoryForm, self).__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']
|