loko/loko/contracts/forms.py

233 lines
No EOL
7.8 KiB
Python

from django import forms
from django.forms import DateInput, formset_factory
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
from .models import (
ContractOrder,
CONTRACT_ORDER_STATUS_CHOICES,
ClaimDeclarationAttachment,
ContractOrderAttachment,
)
from .permissions import get_allowed_contracts_for_user
class ContractOrderForm(forms.ModelForm):
def __init__(self, *args, user=None, project_id=None, **kwargs):
super().__init__(*args, **kwargs)
# Limiter les contrats visibles par l'utilisateur
if user is not None:
self.fields['contract'].queryset = get_allowed_contracts_for_user(user)
# Si des interventions existent pour cet ordre → on fige le contrat (UI)
if self.instance and self.instance.pk:
has_interventions = self.instance.interventions.exists() # via related_name="interventions"
if has_interventions:
self.fields['contract'].disabled = True
self.fields['contract'].help_text = _("Ce contrat ne peut plus être modifié car des interventions sont déjà liées.")
# Ajout de la classe Bootstrap à tous les champs
for field_name, field in self.fields.items():
widget = field.widget
base_class = ''
if isinstance(widget, forms.Select):
base_class = 'form-select'
else:
base_class = 'form-control'
# Marquer les champs optionnels avec une classe supplémentaire
if not field.required:
widget.attrs['class'] = f"{base_class} optional-field"
else:
widget.attrs['class'] = base_class
# Remettre le champ vide si la valeur est 0
if self.initial.get('total_amount') in [0, 0.0] or self.instance.total_amount in [0, 0.0]:
self.initial['total_amount'] = ''
if self.instance and self.instance.pk:
self.fields['order_date'].initial = self.instance.order_date.strftime('%Y-%m-%d')
self.fields['delivery_date'].initial = self.instance.delivery_date.strftime('%Y-%m-%d')
def clean(self):
cleaned = super().clean()
# Sécurité serveur : si des interventions existent, interdire toute modif du contrat
if self.instance and self.instance.pk and self.instance.interventions.exists():
if 'contract' in self.changed_data:
self.add_error(
'contract',
_("Le contrat ne peut plus être modifié car des interventions sont déjà liées à cette commande.")
)
raise ValidationError(_("Modification du contrat interdite."))
return cleaned
order_date = forms.DateField(
required=True,
widget=DateInput(
attrs={'type': 'date', 'class': 'form-control'},
format='%Y-%m-%d' # Format ISO compatible avec HTML5
),
label=_("Date de la commande")
)
delivery_date = forms.DateField(
required=True,
widget=DateInput(
attrs={'type': 'date', 'class': 'form-control'},
format='%Y-%m-%d'
),
label=_("Date de livraison")
)
order_status = forms.ChoiceField(
choices=CONTRACT_ORDER_STATUS_CHOICES,
widget=forms.Select(attrs={'class': 'form-select'}),
label=_("Statut de la commande")
)
order_file = forms.FileField(
required=False,
widget=forms.ClearableFileInput(attrs={'class': 'form-control'}),
label=_("Fichier de commande")
)
total_amount = forms.DecimalField(
required=False,
label=_("Montant total (€)"),
widget=forms.NumberInput(attrs={
'step': '0.01',
'placeholder': 'ex. 1000.00',
})
)
class Meta:
model = ContractOrder
fields = [
'contract',
'order_code',
'description',
'order_date',
'delivery_date',
'total_amount',
'order_status',
'order_file'
]
widgets = {
'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
}
labels = {
'contract': _("Contrat"),
'order_code': _("Référence de commande"),
'description': _("Description"),
}
class ContractOrderQuoteQuantityForm(forms.Form):
contract_post_id = forms.IntegerField(
min_value=1,
widget=forms.HiddenInput(),
)
quantity = forms.DecimalField(
label=_("Quantité"),
decimal_places=2,
max_digits=10,
min_value=Decimal('0'),
required=False,
widget=forms.NumberInput(
attrs={
'class': 'form-control form-control-sm text-end quote-quantity-input',
'step': '1',
'min': '0',
'inputmode': 'decimal',
'style': 'text-align: center;',
}
),
)
def __init__(self, *args, allowed_post_ids=None, disabled=False, **kwargs):
self.allowed_post_ids = allowed_post_ids
self._is_disabled = disabled
super().__init__(*args, **kwargs)
if self._is_disabled:
self.fields['quantity'].disabled = True
css_class = self.fields['quantity'].widget.attrs.get('class', '')
self.fields['quantity'].widget.attrs['class'] = f"{css_class} bg-light".strip()
def clean_contract_post_id(self):
value = self.cleaned_data.get('contract_post_id')
if not value:
raise ValidationError(_("Poste invalide."))
if self.allowed_post_ids is not None and value not in self.allowed_post_ids:
raise ValidationError(_("Poste invalide."))
return value
def clean_quantity(self):
quantity = self.cleaned_data.get('quantity')
if quantity in (None, ''):
return Decimal('0')
return quantity
ContractOrderQuoteQuantityFormSet = formset_factory(
ContractOrderQuoteQuantityForm,
extra=0,
)
class ClaimDeclarationAttachmentForm(forms.ModelForm):
"""Formulaire pour ajouter une pièce jointe à une déclaration de créance"""
class Meta:
model = ClaimDeclarationAttachment
fields = ['title', 'document']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Ex: Facture fournisseur, Bon de livraison...'),
}),
'document': forms.ClearableFileInput(attrs={
'class': 'form-control',
}),
}
labels = {
'title': _("Titre du justificatif"),
'document': _("Fichier"),
}
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return [item for item in result if item]
class ContractOrderAttachmentForm(forms.ModelForm):
"""Formulaire pour ajouter des documents (photos, PDF, …) à une commande."""
files = MultipleFileField(required=True, label=_("Fichiers"))
class Meta:
model = ContractOrderAttachment
fields = ['files']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['files'].widget.attrs.update({'class': 'form-control'})