barbacompta/eo_gestion/eo_facture/forms.py

211 lines
7.4 KiB
Python

# barbacompta - invoicing for dummies
# Copyright (C) 2019-2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
from decimal import Decimal
from django import forms
from django.contrib.admin import widgets
from django.core.exceptions import ValidationError
from django.db.transaction import atomic
from . import models
pourcentages = [(Decimal(i) / 100, "%s %%" % i) for i in range(0, 101, 5)]
class RapidFactureForm(forms.Form):
contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
pourcentage = forms.ChoiceField(choices=pourcentages, initial='1')
solde = forms.BooleanField(
required=False, label='Ignorer le pourcentage, et solder le contrat en une ligne.'
)
def __init__(self, request=None, *args, **kwargs):
self.request = request
super().__init__(*args, **kwargs)
@atomic
def clean(self):
contrat = self.cleaned_data['contrat']
pourcentage = Decimal(self.cleaned_data['pourcentage'])
solde = self.cleaned_data['solde']
facture = models.Facture(contrat=contrat, creator=self.request.user, taux_tva=contrat.tva)
try:
facture.clean()
except ValidationError as e:
raise forms.ValidationError(*e.args)
facture.save()
self.cleaned_data["facture"] = facture
lignes = []
errors = []
if solde:
montant_solde = contrat.solde()
if montant_solde == 0:
raise ValidationError('Le solde du contrat est déjà nul.')
models.Ligne.objects.create(
facture=facture, intitule='Solde', prix_unitaire_ht=montant_solde, quantite=Decimal(1)
)
else:
for prestation in contrat.prestations.all():
ligne = models.Ligne(
facture=facture,
intitule=prestation.intitule,
prix_unitaire_ht=prestation.prix_unitaire_ht,
quantite=prestation.quantite * pourcentage,
)
lignes.append(ligne)
try:
ligne.clean()
except ValidationError as e:
error = "Il y a un problème avec la ligne « %s »: " % prestation.intitule
error += "; ".join(map(lambda x: x.rstrip("."), e.messages))
errors.append(error)
if errors:
raise forms.ValidationError(errors)
else:
for ligne in lignes:
ligne.save()
return self.cleaned_data
class DuplicateContractForm(forms.Form):
contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
new_intitule = forms.CharField(max_length=150, label="Nouvel intitulé")
def __init__(self, request=None, *args, **kwargs):
self.request = request
super().__init__(*args, **kwargs)
@atomic
def clean(self):
contrat = self.cleaned_data['contrat']
new_intitule = self.cleaned_data['new_intitule']
new_contrat = models.Contrat(
client=contrat.client,
intitule=new_intitule,
description=contrat.description,
tva=contrat.tva,
creator=self.request.user,
)
try:
new_contrat.clean()
except ValidationError as e:
raise forms.ValidationError(*e.args)
new_contrat.save()
self.cleaned_data["new_contrat"] = new_contrat
for prestation in contrat.prestations.all():
new_prestation = prestation.duplicate()
new_prestation.contrat = new_contrat
new_prestation.save()
return self.cleaned_data
class LigneForm(forms.ModelForm):
class Meta:
exclude = ()
model = models.Ligne
localized_fields = ("quantite", "prix_unitaire_ht", "taux_tva")
class PrestationForm(forms.ModelForm):
class Meta:
exclude = ()
model = models.Prestation
localized_fields = ("quantite", "prix_unitaire_ht")
class FactureForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['contrat'].queryset = models.Contrat.objects.none()
client_id = None
if 'client' in self.data:
try:
client_id = int(self.data.get('client'))
except (ValueError, TypeError):
pass # invalid input from client; ignore and fallback to empty Contrat queryset
elif 'client' in self.initial:
try:
client_id = int(self.initial.get('client'))
except (ValueError, TypeError):
pass # invalid input from client; ignore and fallback to empty Contrat queryset
if client_id:
self.fields['contrat'].queryset = models.Contrat.objects.filter(client_id=client_id)
def clean_proforma(self):
if not self.instance.proforma and 'proforma' in self.changed_data:
raise ValidationError('Une facture ne peut pas redevenir proforma.')
return self.cleaned_data['proforma']
def clean_emission(self):
if not self.instance.proforma and 'emission' in self.changed_data:
raise ValidationError('Seules les factures proforma peuvent changer de date d\'émission.')
return self.cleaned_data['emission']
def clean(self):
# réinitialiser la date d'émission quand une facture quitte le statut
# proforma, sauf si une date d'émission spécifique a été fixée à la
# main
update_echeance = False
if self.instance.proforma and 'proforma' in self.changed_data and 'emission' not in self.changed_data:
self.cleaned_data['emission'] = models.today()
update_echeance = True
if (
self.cleaned_data.get('emission')
and 'emission' in self.changed_data
and 'echeance' not in self.changed_data
):
update_echeance = True
if update_echeance:
self.cleaned_data['echeance'] = self.cleaned_data['emission'] + datetime.timedelta(
days=models.DELAI_PAIEMENT
)
class Meta:
exclude = ()
model = models.Facture
localized_fields = ("taux_tva",)
class ClientForm(forms.ModelForm):
class Meta:
exclude = ()
model = models.Client
localized_fields = ("tva",)
widgets = {
"adresse": widgets.AdminTextareaWidget(attrs={"rows": 4}),
"contacts": widgets.AdminTextareaWidget(attrs={"rows": 4}),
}
class ContratForm(forms.ModelForm):
class Meta:
exclude = ()
model = models.Contrat
localized_fields = ("tva", "montant_sous_traite")
class PaymentForm(forms.ModelForm):
class Meta:
exclude = ()
model = models.Payment
localized_fields = ("montant_affecte",)