""" Modèles de données — JB Compta. Conçus pour rester valables si le statut évolue : les taux et seuils ne sont jamais codés en dur, ils vivent dans le modèle `Bareme` (par activité et par année). """ from decimal import Decimal from django.db import models from django.db.models import Sum class Activite(models.TextChoices): VENTE = "vente", "Vente de marchandises (BIC)" SERVICE_BIC = "service_bic", "Prestation de services (BIC)" SERVICE_BNC = "service_bnc", "Prestation de services / libéral (BNC)" class StatutFacture(models.TextChoices): BROUILLON = "brouillon", "Brouillon" ENVOYEE = "envoyee", "Envoyée" ENCAISSEE = "encaissee", "Encaissée" ANNULEE = "annulee", "Annulée" class Bareme(models.Model): """Taux et seuils officiels, par activité et par année (paramétrable).""" activite = models.CharField(max_length=20, choices=Activite.choices) annee = models.PositiveIntegerField() taux_urssaf = models.DecimalField( "Taux cotisations URSSAF (%)", max_digits=5, decimal_places=2) abattement_fiscal = models.DecimalField( "Abattement forfaitaire (%)", max_digits=5, decimal_places=2) taux_vl = models.DecimalField( "Taux versement libératoire (%)", max_digits=4, decimal_places=2) seuil_ca = models.DecimalField( "Plafond CA micro (€)", max_digits=12, decimal_places=2) seuil_tva_base = models.DecimalField( "Seuil franchise TVA (€)", max_digits=12, decimal_places=2) seuil_tva_majore = models.DecimalField( "Seuil franchise TVA majoré (€)", max_digits=12, decimal_places=2) class Meta: verbose_name = "Barème" verbose_name_plural = "Barèmes" unique_together = ("activite", "annee") ordering = ["-annee", "activite"] def __str__(self): return f"{self.get_activite_display()} — {self.annee}" class Entreprise(models.Model): nom = models.CharField(max_length=200) siret = models.CharField(max_length=20, blank=True) activite = models.CharField( "Activité principale", max_length=20, choices=Activite.choices, default=Activite.SERVICE_BNC, help_text="Activité par défaut des nouvelles factures. " "Une entreprise peut avoir plusieurs activités : " "chaque facture/dépense porte la sienne.") franchise_tva = models.BooleanField( "En franchise de TVA", default=False, help_text="Décochez si vous facturez et déclarez la TVA.") taux_tva_defaut = models.DecimalField( "Taux de TVA par défaut (%)", max_digits=4, decimal_places=2, default=Decimal("20.00")) versement_liberatoire = models.BooleanField( "Option versement libératoire", default=False) email = models.EmailField(blank=True) adresse = models.TextField(blank=True) cree_le = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Entreprise" verbose_name_plural = "Entreprises" ordering = ["nom"] def __str__(self): return self.nom def bareme(self, annee): return Bareme.objects.filter(activite=self.activite, annee=annee).first() class Client(models.Model): entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE, related_name="clients") nom = models.CharField(max_length=200) email = models.EmailField(blank=True) siret = models.CharField(max_length=20, blank=True) adresse = models.TextField(blank=True) cree_le = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Client" verbose_name_plural = "Clients" ordering = ["nom"] def __str__(self): return self.nom class Facture(models.Model): entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE, related_name="factures") client = models.ForeignKey(Client, on_delete=models.PROTECT, related_name="factures") numero = models.CharField(max_length=50) activite = models.CharField(max_length=20, choices=Activite.choices) date_emission = models.DateField() date_encaissement = models.DateField( null=True, blank=True, help_text="À renseigner une fois la facture payée. " "Le CA micro est compté à l'encaissement.") statut = models.CharField(max_length=12, choices=StatutFacture.choices, default=StatutFacture.BROUILLON) montant_ht = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal("0")) montant_tva = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal("0")) montant_ttc = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal("0")) notes = models.TextField(blank=True) cree_le = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Facture" verbose_name_plural = "Factures" unique_together = ("entreprise", "numero") ordering = ["-date_emission", "numero"] def __str__(self): return f"{self.numero} — {self.client}" def recompute_totaux(self, save=True): """Recalcule HT/TVA/TTC à partir des lignes.""" ht = Decimal("0") tva = Decimal("0") for ligne in self.lignes.all(): l_ht = ligne.montant_ht ht += l_ht tva += (l_ht * ligne.taux_tva / Decimal("100")) self.montant_ht = ht.quantize(Decimal("0.01")) self.montant_tva = tva.quantize(Decimal("0.01")) self.montant_ttc = (self.montant_ht + self.montant_tva) if save: super().save(update_fields=["montant_ht", "montant_tva", "montant_ttc"]) class LigneFacture(models.Model): facture = models.ForeignKey(Facture, on_delete=models.CASCADE, related_name="lignes") designation = models.CharField(max_length=255) quantite = models.DecimalField(max_digits=12, decimal_places=3, default=Decimal("1")) prix_unitaire_ht = models.DecimalField(max_digits=12, decimal_places=2) taux_tva = models.DecimalField(max_digits=4, decimal_places=2, default=Decimal("0")) class Meta: verbose_name = "Ligne de facture" verbose_name_plural = "Lignes de facture" def __str__(self): return self.designation @property def montant_ht(self): return (self.quantite * self.prix_unitaire_ht).quantize(Decimal("0.01")) class Depense(models.Model): entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE, related_name="depenses") date = models.DateField() activite = models.CharField( max_length=20, choices=Activite.choices, blank=True, help_text="Activité concernée (facultatif, pour la ventilation).") categorie = models.CharField(max_length=100, blank=True) libelle = models.CharField(max_length=255) montant_ttc = models.DecimalField(max_digits=12, decimal_places=2) montant_tva = models.DecimalField( "TVA déductible (€)", max_digits=12, decimal_places=2, default=Decimal("0")) cree_le = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "Dépense" verbose_name_plural = "Dépenses" ordering = ["-date"] def __str__(self): return f"{self.date} — {self.libelle}" @property def montant_ht(self): return (self.montant_ttc - self.montant_tva).quantize(Decimal("0.01"))