diff --git a/compta/__pycache__/__init__.cpython-310.pyc b/compta/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..97dd05d Binary files /dev/null and b/compta/__pycache__/__init__.cpython-310.pyc differ diff --git a/compta/__pycache__/admin.cpython-310.pyc b/compta/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..7a98e87 Binary files /dev/null and b/compta/__pycache__/admin.cpython-310.pyc differ diff --git a/compta/__pycache__/apps.cpython-310.pyc b/compta/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..871e9a1 Binary files /dev/null and b/compta/__pycache__/apps.cpython-310.pyc differ diff --git a/compta/__pycache__/models.cpython-310.pyc b/compta/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..0b5461e Binary files /dev/null and b/compta/__pycache__/models.cpython-310.pyc differ diff --git a/compta/__pycache__/services.cpython-310.pyc b/compta/__pycache__/services.cpython-310.pyc new file mode 100644 index 0000000..18f3e3e Binary files /dev/null and b/compta/__pycache__/services.cpython-310.pyc differ diff --git a/compta/__pycache__/urls.cpython-310.pyc b/compta/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..accb77d Binary files /dev/null and b/compta/__pycache__/urls.cpython-310.pyc differ diff --git a/compta/__pycache__/views.cpython-310.pyc b/compta/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..759d18e Binary files /dev/null and b/compta/__pycache__/views.cpython-310.pyc differ diff --git a/compta/__pycache__/views_saisie.cpython-310.pyc b/compta/__pycache__/views_saisie.cpython-310.pyc new file mode 100644 index 0000000..acb90aa Binary files /dev/null and b/compta/__pycache__/views_saisie.cpython-310.pyc differ diff --git a/compta/admin.py b/compta/admin.py index 24cb94c..442734a 100644 --- a/compta/admin.py +++ b/compta/admin.py @@ -3,9 +3,18 @@ from django.utils import timezone from .models import ( Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Paiement, + Categorie, ) +@admin.register(Categorie) +class CategorieAdmin(admin.ModelAdmin): + list_display = ("nom", "usage", "exclure_calculs", "actif", "ordre") + list_filter = ("usage", "exclure_calculs", "actif") + list_editable = ("usage", "exclure_calculs", "actif", "ordre") + search_fields = ("nom",) + + @admin.register(Bareme) class BaremeAdmin(admin.ModelAdmin): list_display = ("annee", "activite", "taux_urssaf", "abattement_fiscal", @@ -15,8 +24,8 @@ class BaremeAdmin(admin.ModelAdmin): @admin.register(Entreprise) class EntrepriseAdmin(admin.ModelAdmin): - list_display = ("nom", "activite", "franchise_tva", - "versement_liberatoire", "num_tva_intracom", "email") + list_display = ("nom", "activite", "franchise_tva", "versement_liberatoire", + "num_tva_intracom", "vue_tva", "email") list_filter = ("activite", "franchise_tva", "versement_liberatoire") search_fields = ("nom", "siret", "num_tva_intracom") @@ -58,8 +67,8 @@ class FactureAdmin(admin.ModelAdmin): class DepenseInline(admin.TabularInline): model = Depense extra = 0 - fields = ("date", "libelle", "activite", "regime_tva", "taux_tva", - "montant_ttc", "montant_tva") + fields = ("date", "libelle", "categorie", "activite", "regime_tva", + "taux_tva", "montant_ttc", "montant_tva") @admin.register(Paiement) @@ -86,10 +95,11 @@ class PaiementAdmin(admin.ModelAdmin): @admin.register(Depense) class DepenseAdmin(admin.ModelAdmin): - list_display = ("date", "entreprise", "libelle", "activite", "regime_tva", - "montant_ttc", "montant_tva", "tva_autoliq", "paiement") - list_filter = ("entreprise", "regime_tva", "activite", "categorie", "date") - search_fields = ("libelle", "categorie") + list_display = ("date", "entreprise", "libelle", "categorie", "activite", + "regime_tva", "montant_ttc", "montant_tva", "tva_autoliq", + "paiement") + list_filter = ("entreprise", "categorie", "regime_tva", "activite", "date") + search_fields = ("libelle",) date_hierarchy = "date" @admin.display(description="TVA auto-liquidée") diff --git a/compta/management/commands/__pycache__/__init__.cpython-310.pyc b/compta/management/commands/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..bfc5c52 Binary files /dev/null and b/compta/management/commands/__pycache__/__init__.cpython-310.pyc differ diff --git a/compta/management/commands/__pycache__/seed_demo.cpython-310.pyc b/compta/management/commands/__pycache__/seed_demo.cpython-310.pyc new file mode 100644 index 0000000..03fcc92 Binary files /dev/null and b/compta/management/commands/__pycache__/seed_demo.cpython-310.pyc differ diff --git a/compta/management/commands/__pycache__/seed_reference.cpython-310.pyc b/compta/management/commands/__pycache__/seed_reference.cpython-310.pyc new file mode 100644 index 0000000..bacd0e6 Binary files /dev/null and b/compta/management/commands/__pycache__/seed_reference.cpython-310.pyc differ diff --git a/compta/management/commands/seed_demo.py b/compta/management/commands/seed_demo.py index db22e50..8e4fa5c 100644 --- a/compta/management/commands/seed_demo.py +++ b/compta/management/commands/seed_demo.py @@ -1,5 +1,8 @@ """ -Charge les barèmes 2026 et un jeu de données d'exemple (multi-activité + autoliq). +Charge les données de RÉFÉRENCE (barèmes + catégories) puis un jeu de +données d'EXEMPLE (entreprise, clients, factures, dépenses). +Pour la prod, préférez `seed_reference` (référence seule, sans démo). + Usage : python manage.py seed_demo """ from decimal import Decimal @@ -8,43 +11,24 @@ from datetime import date from django.core.management.base import BaseCommand from compta.models import ( - Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Activite, + Entreprise, Client, Facture, LigneFacture, Depense, Activite, StatutFacture, RegimeTva, ) - -# Barèmes officiels 2026 (voir docs/regles-fiscales-2026.md) -BAREMES_2026 = [ - # activite, taux_urssaf, abattement, taux_vl, seuil_ca, tva_base, tva_majore - (Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"), - Decimal("203100"), Decimal("85000"), Decimal("93500")), - (Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"), - Decimal("83600"), Decimal("37500"), Decimal("41250")), - (Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"), - Decimal("83600"), Decimal("37500"), Decimal("41250")), -] +from compta.management.commands.seed_reference import charger_reference class Command(BaseCommand): - help = "Charge les barèmes 2026 et des données d'exemple." + help = "Charge la référence + des données d'exemple." def handle(self, *args, **options): - for act, urssaf, ab, vl, sca, tb, tm in BAREMES_2026: - Bareme.objects.update_or_create( - activite=act, annee=2026, - defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab, - taux_vl=vl, seuil_ca=sca, - seuil_tva_base=tb, seuil_tva_majore=tm), - ) - self.stdout.write(self.style.SUCCESS("Barèmes 2026 chargés.")) + cat = charger_reference(self.stdout, self.style) - # Entreprise multi-activité : impression 3D (Vente BIC) + aide info (Presta BIC) ent, _ = Entreprise.objects.get_or_create( nom="JB Conseil", defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False, taux_tva_defaut=Decimal("20.00"), versement_liberatoire=True, email="jb@example.fr", - num_tva_intracom="FR00123456789"), - ) + num_tva_intracom="FR00123456789")) clients = {} for nom, mail in [("Acme SARL", "contact@acme.fr"), @@ -56,7 +40,6 @@ class Command(BaseCommand): V, S = Activite.VENTE, Activite.SERVICE_BIC factures = [ - # numero, client, activite, emission, encaissement, statut, lignes ("2026-001", "Acme SARL", S, date(2026, 1, 10), date(2026, 1, 25), StatutFacture.ENCAISSEE, [("Dépannage informatique", 2, 600)]), ("2026-002", "Studio Lumiere", V, date(2026, 1, 18), date(2026, 2, 5), @@ -73,8 +56,7 @@ class Command(BaseCommand): entreprise=ent, numero=numero, defaults=dict(client=clients[client_nom], activite=activite, date_emission=emis, date_encaissement=enc, - statut=statut), - ) + statut=statut)) if created: for des, qte, pu in lignes: LigneFacture.objects.create( @@ -84,18 +66,21 @@ class Command(BaseCommand): Depense.objects.get_or_create( entreprise=ent, libelle="Bobines PLA/PETG", date=date(2026, 1, 15), - defaults=dict(categorie="Matiere", activite=Activite.VENTE, + defaults=dict(categorie=cat["Matière première"], activite=Activite.VENTE, montant_ttc=Decimal("96.00"), montant_tva=Decimal("16.00"))) Depense.objects.get_or_create( entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10), - defaults=dict(categorie="Logiciel", activite=Activite.SERVICE_BIC, + defaults=dict(categorie=cat["Abonnement logiciel"], + activite=Activite.SERVICE_BIC, montant_ttc=Decimal("34.80"), montant_tva=Decimal("5.80"))) - # Achat intracommunautaire (ex. imprimante depuis l'Allemagne) : - # le fournisseur facture HT, on auto-liquide la TVA à 20 %. Depense.objects.get_or_create( entreprise=ent, libelle="Imprimante 3D (DE)", date=date(2026, 2, 20), - defaults=dict(categorie="Materiel", activite=Activite.VENTE, + defaults=dict(categorie=cat["Matériel et outillage"], activite=Activite.VENTE, regime_tva=RegimeTva.INTRACOM, taux_tva=Decimal("20"), montant_ttc=Decimal("500.00"), montant_tva=Decimal("0"))) + Depense.objects.get_or_create( + entreprise=ent, libelle="URSSAF DE LORRAINE", date=date(2026, 3, 3), + defaults=dict(categorie=cat["Cotisation sociale Urssaf"], + montant_ttc=Decimal("73.00"), montant_tva=Decimal("0"))) self.stdout.write(self.style.SUCCESS("Données d'exemple chargées.")) diff --git a/compta/management/commands/seed_reference.py b/compta/management/commands/seed_reference.py new file mode 100644 index 0000000..60b3c4b --- /dev/null +++ b/compta/management/commands/seed_reference.py @@ -0,0 +1,60 @@ +""" +Charge les DONNÉES DE RÉFÉRENCE (barèmes + catégories) — sans données de démo. +Idempotent : sûr à relancer en production après chaque mise à jour. + +Usage : python manage.py seed_reference +""" +from decimal import Decimal + +from django.core.management.base import BaseCommand + +from compta.models import Bareme, Categorie, Activite + +BAREMES_2026 = [ + (Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"), + Decimal("203100"), Decimal("85000"), Decimal("93500")), + (Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"), + Decimal("83600"), Decimal("37500"), Decimal("41250")), + (Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"), + Decimal("83600"), Decimal("37500"), Decimal("41250")), +] + +# exclure_calculs=True => mouvement neutre (hors calculs fiscaux). +CATEGORIES = [ + ("Vente de produits finis", "vente", False, 1), + ("Prestation de services", "vente", False, 2), + ("Matériel et outillage", "depense", False, 1), + ("Matière première", "depense", False, 2), + ("Télécom, fournitures, documentation", "depense", False, 3), + ("Frais de repas hors domicile", "depense", False, 4), + ("Abonnement logiciel", "depense", False, 5), + ("TVA payée", "neutre", True, 1), + ("Cotisation sociale Urssaf", "neutre", True, 2), + ("Remboursement de TVA", "neutre", True, 3), + ("Prélèvement personnel", "neutre", True, 4), + ("Virement interne", "neutre", True, 5), +] + + +def charger_reference(stdout=None, style=None): + for act, urssaf, ab, vl, sca, tb, tm in BAREMES_2026: + Bareme.objects.update_or_create( + activite=act, annee=2026, + defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab, taux_vl=vl, + seuil_ca=sca, seuil_tva_base=tb, seuil_tva_majore=tm)) + cat = {} + for nom, usage, excl, ordre in CATEGORIES: + c, _ = Categorie.objects.update_or_create( + nom=nom, defaults=dict(usage=usage, exclure_calculs=excl, ordre=ordre)) + cat[nom] = c + if stdout and style: + stdout.write(style.SUCCESS( + f"Référence chargée : {len(BAREMES_2026)} barèmes, {len(cat)} catégories.")) + return cat + + +class Command(BaseCommand): + help = "Charge les barèmes et catégories de référence (sans démo)." + + def handle(self, *args, **options): + charger_reference(self.stdout, self.style) diff --git a/compta/migrations/0005_categorie_entreprise_vue_tva_alter_depense_categorie.py b/compta/migrations/0005_categorie_entreprise_vue_tva_alter_depense_categorie.py new file mode 100644 index 0000000..9fea6c1 --- /dev/null +++ b/compta/migrations/0005_categorie_entreprise_vue_tva_alter_depense_categorie.py @@ -0,0 +1,46 @@ +# Catégories globales + vue TVA + passage de Depense.categorie en clé étrangère. +# Note : on remplace l'ancien champ texte `categorie` par une FK plutôt que de +# l'altérer (un AlterField texte->FK échouerait sur PostgreSQL avec des données). +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compta', '0004_paiement_depense_paiement'), + ] + + operations = [ + migrations.CreateModel( + name='Categorie', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nom', models.CharField(max_length=100, unique=True)), + ('usage', models.CharField(choices=[('depense', 'Dépense'), ('vente', 'Vente'), ('neutre', 'Mouvement neutre')], default='depense', max_length=10)), + ('exclure_calculs', models.BooleanField(default=False, help_text='Cochez pour les mouvements non fiscaux : TVA payée, cotisations URSSAF, remboursement de TVA, virements…', verbose_name='Neutre (exclue des calculs)')), + ('actif', models.BooleanField(default=True)), + ('ordre', models.IntegerField(default=0)), + ], + options={ + 'verbose_name': 'Catégorie', + 'verbose_name_plural': 'Catégories', + 'ordering': ['usage', 'ordre', 'nom'], + }, + ), + migrations.AddField( + model_name='entreprise', + name='vue_tva', + field=models.CharField(choices=[('simplifie', 'Résumé simplifié'), ('ca3', 'Champs CA3 officiels')], default='simplifie', max_length=10, verbose_name="Mode d'affichage de la déclaration TVA"), + ), + # Ancien champ texte abandonné, remplacé par une FK propre. + migrations.RemoveField( + model_name='depense', + name='categorie', + ), + migrations.AddField( + model_name='depense', + name='categorie', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='depenses', to='compta.categorie'), + ), + ] diff --git a/compta/migrations/__pycache__/0001_initial.cpython-310.pyc b/compta/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..665cab3 Binary files /dev/null and b/compta/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/compta/migrations/__pycache__/0002_depense_activite_alter_entreprise_activite.cpython-310.pyc b/compta/migrations/__pycache__/0002_depense_activite_alter_entreprise_activite.cpython-310.pyc new file mode 100644 index 0000000..67c4a01 Binary files /dev/null and b/compta/migrations/__pycache__/0002_depense_activite_alter_entreprise_activite.cpython-310.pyc differ diff --git a/compta/migrations/__pycache__/0003_depense_regime_tva_depense_taux_tva_and_more.cpython-310.pyc b/compta/migrations/__pycache__/0003_depense_regime_tva_depense_taux_tva_and_more.cpython-310.pyc new file mode 100644 index 0000000..afc3840 Binary files /dev/null and b/compta/migrations/__pycache__/0003_depense_regime_tva_depense_taux_tva_and_more.cpython-310.pyc differ diff --git a/compta/migrations/__pycache__/0004_paiement_depense_paiement.cpython-310.pyc b/compta/migrations/__pycache__/0004_paiement_depense_paiement.cpython-310.pyc new file mode 100644 index 0000000..3ce7cc6 Binary files /dev/null and b/compta/migrations/__pycache__/0004_paiement_depense_paiement.cpython-310.pyc differ diff --git a/compta/migrations/__pycache__/0005_categorie_entreprise_vue_tva_alter_depense_categorie.cpython-310.pyc b/compta/migrations/__pycache__/0005_categorie_entreprise_vue_tva_alter_depense_categorie.cpython-310.pyc new file mode 100644 index 0000000..3973bf4 Binary files /dev/null and b/compta/migrations/__pycache__/0005_categorie_entreprise_vue_tva_alter_depense_categorie.cpython-310.pyc differ diff --git a/compta/migrations/__pycache__/__init__.cpython-310.pyc b/compta/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a5e4238 Binary files /dev/null and b/compta/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/compta/models.py b/compta/models.py index 0ce78df..4a9e2c6 100644 --- a/compta/models.py +++ b/compta/models.py @@ -31,6 +31,36 @@ class RegimeTva(models.TextChoices): IMPORT = "import", "Import hors UE (autoliquidation)" +class Categorie(models.Model): + """Catégorie globale (partagée entre entreprises). + + Une catégorie « neutre » (exclure_calculs=True) sert au suivi de trésorerie + mais n'entre PAS dans les calculs fiscaux : paiement d'URSSAF, de TVA, + remboursement de TVA, prélèvement personnel, virement interne… + """ + USAGE = [ + ("depense", "Dépense"), + ("vente", "Vente"), + ("neutre", "Mouvement neutre"), + ] + nom = models.CharField(max_length=100, unique=True) + usage = models.CharField(max_length=10, choices=USAGE, default="depense") + exclure_calculs = models.BooleanField( + "Neutre (exclue des calculs)", default=False, + help_text="Cochez pour les mouvements non fiscaux : TVA payée, " + "cotisations URSSAF, remboursement de TVA, virements…") + actif = models.BooleanField(default=True) + ordre = models.IntegerField(default=0) + + class Meta: + verbose_name = "Catégorie" + verbose_name_plural = "Catégories" + ordering = ["usage", "ordre", "nom"] + + def __str__(self): + return self.nom + + 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) @@ -59,6 +89,7 @@ class Bareme(models.Model): class Entreprise(models.Model): + VUE_TVA = [("simplifie", "Résumé simplifié"), ("ca3", "Champs CA3 officiels")] nom = models.CharField(max_length=200) siret = models.CharField(max_length=20, blank=True) activite = models.CharField( @@ -77,6 +108,9 @@ class Entreprise(models.Model): "Option versement libératoire", default=False) num_tva_intracom = models.CharField( "N° TVA intracommunautaire", max_length=20, blank=True) + vue_tva = models.CharField( + "Mode d'affichage de la déclaration TVA", max_length=10, + choices=VUE_TVA, default="simplifie") email = models.EmailField(blank=True) adresse = models.TextField(blank=True) cree_le = models.DateTimeField(auto_now_add=True) @@ -225,7 +259,8 @@ class Depense(models.Model): 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) + categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, + null=True, blank=True, related_name="depenses") libelle = models.CharField(max_length=255) montant_ttc = models.DecimalField( "Montant payé (€)", max_digits=12, decimal_places=2, @@ -251,6 +286,11 @@ class Depense(models.Model): def __str__(self): return f"{self.date} — {self.libelle}" + @property + def neutre(self): + """Mouvement neutre : exclu des calculs fiscaux.""" + return bool(self.categorie and self.categorie.exclure_calculs) + @property def autoliquidee(self): """True si la TVA est auto-liquidée (achat UE ou import).""" @@ -266,14 +306,17 @@ class Depense(models.Model): @property def tva_autoliquidee(self): """TVA française auto-liquidée (comptée en collectée ET en déductible).""" - if self.autoliquidee: + if self.autoliquidee and not self.neutre: return (self.montant_ttc * self.taux_tva / Decimal("100")).quantize( Decimal("0.01")) return Decimal("0.00") @property def tva_deductible_value(self): - """TVA récupérable : facturée (national) ou auto-liquidée (UE/import).""" + """TVA récupérable : facturée (national) ou auto-liquidée (UE/import). + Nulle pour un mouvement neutre.""" + if self.neutre: + return Decimal("0.00") if self.autoliquidee: return self.tva_autoliquidee return self.montant_tva diff --git a/compta/templates/compta/saisie_paiement.html b/compta/templates/compta/saisie_paiement.html index 881778e..de3ba61 100644 --- a/compta/templates/compta/saisie_paiement.html +++ b/compta/templates/compta/saisie_paiement.html @@ -80,11 +80,11 @@
| Libellé / vendeur | -Catégorie | -Activité | -Régime TVA | -Taux % | +Libellé / vendeur | +Catégorie | +Activité | +Régime TVA | +Taux % | Montant € | TVA déduct. € | @@ -111,7 +111,12 @@ | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| - | + | + + |