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'})