Autoliquidation TVA (achats UE/import)

This commit is contained in:
2026-06-30 00:10:47 +02:00
parent 1716cebfbd
commit 6856734de7
7 changed files with 161 additions and 27 deletions
+9 -6
View File
@@ -16,9 +16,9 @@ class BaremeAdmin(admin.ModelAdmin):
@admin.register(Entreprise) @admin.register(Entreprise)
class EntrepriseAdmin(admin.ModelAdmin): class EntrepriseAdmin(admin.ModelAdmin):
list_display = ("nom", "activite", "franchise_tva", list_display = ("nom", "activite", "franchise_tva",
"versement_liberatoire", "email") "versement_liberatoire", "num_tva_intracom", "email")
list_filter = ("activite", "franchise_tva", "versement_liberatoire") list_filter = ("activite", "franchise_tva", "versement_liberatoire")
search_fields = ("nom", "siret") search_fields = ("nom", "siret", "num_tva_intracom")
@admin.register(Client) @admin.register(Client)
@@ -52,14 +52,17 @@ class FactureAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change): def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change) super().save_related(request, form, formsets, change)
# Recalcule les totaux à partir des lignes après sauvegarde
form.instance.recompute_totaux() form.instance.recompute_totaux()
@admin.register(Depense) @admin.register(Depense)
class DepenseAdmin(admin.ModelAdmin): class DepenseAdmin(admin.ModelAdmin):
list_display = ("date", "entreprise", "libelle", "activite", "categorie", list_display = ("date", "entreprise", "libelle", "activite", "regime_tva",
"montant_ttc", "montant_tva") "montant_ttc", "montant_tva", "tva_autoliq")
list_filter = ("entreprise", "activite", "categorie", "date") list_filter = ("entreprise", "regime_tva", "activite", "categorie", "date")
search_fields = ("libelle", "categorie") search_fields = ("libelle", "categorie")
date_hierarchy = "date" date_hierarchy = "date"
@admin.display(description="TVA auto-liquidée")
def tva_autoliq(self, obj):
return obj.tva_autoliquidee
+11 -3
View File
@@ -1,5 +1,5 @@
""" """
Charge les barèmes 2026 et un jeu de données d'exemple (multi-activité). Charge les barèmes 2026 et un jeu de données d'exemple (multi-activité + autoliq).
Usage : python manage.py seed_demo Usage : python manage.py seed_demo
""" """
from decimal import Decimal from decimal import Decimal
@@ -9,7 +9,7 @@ from django.core.management.base import BaseCommand
from compta.models import ( from compta.models import (
Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Activite, Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Activite,
StatutFacture, StatutFacture, RegimeTva,
) )
# Barèmes officiels 2026 (voir docs/regles-fiscales-2026.md) # Barèmes officiels 2026 (voir docs/regles-fiscales-2026.md)
@@ -42,7 +42,8 @@ class Command(BaseCommand):
nom="JB Conseil", nom="JB Conseil",
defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False, defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False,
taux_tva_defaut=Decimal("20.00"), taux_tva_defaut=Decimal("20.00"),
versement_liberatoire=True, email="jb@example.fr"), versement_liberatoire=True, email="jb@example.fr",
num_tva_intracom="FR00123456789"),
) )
clients = {} clients = {}
@@ -89,5 +90,12 @@ class Command(BaseCommand):
entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10), entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10),
defaults=dict(categorie="Logiciel", activite=Activite.SERVICE_BIC, defaults=dict(categorie="Logiciel", activite=Activite.SERVICE_BIC,
montant_ttc=Decimal("34.80"), montant_tva=Decimal("5.80"))) 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,
regime_tva=RegimeTva.INTRACOM, taux_tva=Decimal("20"),
montant_ttc=Decimal("500.00"), montant_tva=Decimal("0")))
self.stdout.write(self.style.SUCCESS("Données d'exemple chargées.")) self.stdout.write(self.style.SUCCESS("Données d'exemple chargées."))
@@ -0,0 +1,39 @@
# Generated by Django 5.2.15 on 2026-06-29 22:08
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compta', '0002_depense_activite_alter_entreprise_activite'),
]
operations = [
migrations.AddField(
model_name='depense',
name='regime_tva',
field=models.CharField(choices=[('nationale', 'Nationale (TVA française facturée)'), ('intracom', 'Intracommunautaire (autoliquidation)'), ('import', 'Import hors UE (autoliquidation)')], default='nationale', max_length=12, verbose_name='Régime de TVA'),
),
migrations.AddField(
model_name='depense',
name='taux_tva',
field=models.DecimalField(decimal_places=2, default=Decimal('0'), help_text='Taux français à auto-liquider pour les achats UE/import (ex. 20).', max_digits=4, verbose_name='Taux TVA autoliquidation (%)'),
),
migrations.AddField(
model_name='entreprise',
name='num_tva_intracom',
field=models.CharField(blank=True, max_length=20, verbose_name='N° TVA intracommunautaire'),
),
migrations.AlterField(
model_name='depense',
name='montant_ttc',
field=models.DecimalField(decimal_places=2, help_text="Pour un achat UE/import, c'est le montant HT (sans TVA facturée).", max_digits=12, verbose_name='Montant payé (€)'),
),
migrations.AlterField(
model_name='depense',
name='montant_tva',
field=models.DecimalField(decimal_places=2, default=Decimal('0'), help_text='TVA payée au fournisseur (achats nationaux uniquement).', max_digits=12, verbose_name='TVA déductible facturée (€)'),
),
]
+45 -3
View File
@@ -24,6 +24,13 @@ class StatutFacture(models.TextChoices):
ANNULEE = "annulee", "Annulée" ANNULEE = "annulee", "Annulée"
class RegimeTva(models.TextChoices):
"""Régime de TVA d'une dépense (achat)."""
NATIONALE = "nationale", "Nationale (TVA française facturée)"
INTRACOM = "intracom", "Intracommunautaire (autoliquidation)"
IMPORT = "import", "Import hors UE (autoliquidation)"
class Bareme(models.Model): class Bareme(models.Model):
"""Taux et seuils officiels, par activité et par année (paramétrable).""" """Taux et seuils officiels, par activité et par année (paramétrable)."""
activite = models.CharField(max_length=20, choices=Activite.choices) activite = models.CharField(max_length=20, choices=Activite.choices)
@@ -68,6 +75,8 @@ class Entreprise(models.Model):
default=Decimal("20.00")) default=Decimal("20.00"))
versement_liberatoire = models.BooleanField( versement_liberatoire = models.BooleanField(
"Option versement libératoire", default=False) "Option versement libératoire", default=False)
num_tva_intracom = models.CharField(
"N° TVA intracommunautaire", max_length=20, blank=True)
email = models.EmailField(blank=True) email = models.EmailField(blank=True)
adresse = models.TextField(blank=True) adresse = models.TextField(blank=True)
cree_le = models.DateTimeField(auto_now_add=True) cree_le = models.DateTimeField(auto_now_add=True)
@@ -180,10 +189,20 @@ class Depense(models.Model):
help_text="Activité concernée (facultatif, pour la ventilation).") help_text="Activité concernée (facultatif, pour la ventilation).")
categorie = models.CharField(max_length=100, blank=True) categorie = models.CharField(max_length=100, blank=True)
libelle = models.CharField(max_length=255) libelle = models.CharField(max_length=255)
montant_ttc = models.DecimalField(max_digits=12, decimal_places=2) montant_ttc = models.DecimalField(
"Montant payé (€)", max_digits=12, decimal_places=2,
help_text="Pour un achat UE/import, c'est le montant HT (sans TVA facturée).")
montant_tva = models.DecimalField( montant_tva = models.DecimalField(
"TVA déductible (€)", max_digits=12, decimal_places=2, "TVA déductible facturée (€)", max_digits=12, decimal_places=2,
default=Decimal("0")) default=Decimal("0"),
help_text="TVA payée au fournisseur (achats nationaux uniquement).")
regime_tva = models.CharField(
"Régime de TVA", max_length=12, choices=RegimeTva.choices,
default=RegimeTva.NATIONALE)
taux_tva = models.DecimalField(
"Taux TVA autoliquidation (%)", max_digits=4, decimal_places=2,
default=Decimal("0"),
help_text="Taux français à auto-liquider pour les achats UE/import (ex. 20).")
cree_le = models.DateTimeField(auto_now_add=True) cree_le = models.DateTimeField(auto_now_add=True)
class Meta: class Meta:
@@ -194,6 +213,29 @@ class Depense(models.Model):
def __str__(self): def __str__(self):
return f"{self.date}{self.libelle}" return f"{self.date}{self.libelle}"
@property
def autoliquidee(self):
"""True si la TVA est auto-liquidée (achat UE ou import)."""
return self.regime_tva in (RegimeTva.INTRACOM, RegimeTva.IMPORT)
@property @property
def montant_ht(self): def montant_ht(self):
# En autoliquidation, le fournisseur ne facture pas de TVA : HT = montant payé.
if self.autoliquidee:
return self.montant_ttc.quantize(Decimal("0.01"))
return (self.montant_ttc - self.montant_tva).quantize(Decimal("0.01")) return (self.montant_ttc - self.montant_tva).quantize(Decimal("0.01"))
@property
def tva_autoliquidee(self):
"""TVA française auto-liquidée (comptée en collectée ET en déductible)."""
if self.autoliquidee:
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)."""
if self.autoliquidee:
return self.tva_autoliquidee
return self.montant_tva
+40 -12
View File
@@ -6,10 +6,14 @@ Bases de calcul : CA HORS TAXES (HT) encaissé.
- En franchise de TVA, HT = TTC : le calcul reste correct. - En franchise de TVA, HT = TTC : le calcul reste correct.
Multi-activité : une entreprise peut exercer plusieurs activités (ex. Vente BIC Multi-activité : une entreprise peut exercer plusieurs activités (ex. Vente BIC
+ Prestation BIC). Les taux URSSAF, abattements, taux de versement libératoire et + Prestation BIC). Taux URSSAF, abattements, taux VL et seuils diffèrent par
seuils diffèrent par activité, donc tout est ventilé par activité puis totalisé. activité, donc tout est ventilé par activité puis totalisé.
TVA à reverser = TVA collectée (ventes encaissées) TVA déductible (dépenses). TVA :
- TVA collectée = TVA sur ventes encaissées + TVA auto-liquidée (achats UE/import).
- TVA déductible = TVA facturée sur achats nationaux + TVA auto-liquidée.
- L'autoliquidation s'ajoute aux deux côtés (effet net souvent nul), mais doit
apparaître sur la CA3 : on l'expose donc séparément.
""" """
from decimal import Decimal from decimal import Decimal
from django.db.models import Sum from django.db.models import Sum
@@ -32,6 +36,10 @@ def factures_encaissees(entreprise, annee):
) )
def depenses_annee(entreprise, annee):
return Depense.objects.filter(entreprise=entreprise, date__year=annee)
def ca_ht_par_activite(entreprise, annee): def ca_ht_par_activite(entreprise, annee):
"""Dict {activite: CA HT encaissé}.""" """Dict {activite: CA HT encaissé}."""
rows = ( rows = (
@@ -47,18 +55,34 @@ def ca_ht(entreprise, annee):
return _q(agg["s"]) return _q(agg["s"])
def tva_collectee(entreprise, annee): # ---- TVA ----------------------------------------------------------------
def tva_sur_ventes(entreprise, annee):
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_tva")) agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_tva"))
return _q(agg["s"]) return _q(agg["s"])
def tva_deductible(entreprise, annee): def tva_autoliquidee(entreprise, annee):
agg = Depense.objects.filter( """TVA auto-liquidée sur les achats UE/import (intracom + import)."""
entreprise=entreprise, date__year=annee total = sum((d.tva_autoliquidee for d in depenses_annee(entreprise, annee)),
).aggregate(s=Sum("montant_tva")) Decimal("0"))
return _q(agg["s"]) return _q(total)
def tva_deductible(entreprise, annee):
"""TVA récupérable : facturée (national) + auto-liquidée (UE/import)."""
total = sum((d.tva_deductible_value for d in depenses_annee(entreprise, annee)),
Decimal("0"))
return _q(total)
def tva_collectee(entreprise, annee):
"""TVA due : sur ventes + auto-liquidée sur achats UE/import."""
return _q(tva_sur_ventes(entreprise, annee) + tva_autoliquidee(entreprise, annee))
# ---- CA mensuel ---------------------------------------------------------
def ca_ht_mensuel(entreprise, annee): def ca_ht_mensuel(entreprise, annee):
"""Liste de 12 valeurs (index 0 = janvier) du CA HT encaissé (toutes activités).""" """Liste de 12 valeurs (index 0 = janvier) du CA HT encaissé (toutes activités)."""
rows = ( rows = (
@@ -74,6 +98,8 @@ def ca_ht_mensuel(entreprise, annee):
return mensuel return mensuel
# ---- Détail par activité ------------------------------------------------
def detail_activite(activite, ca, bareme): def detail_activite(activite, ca, bareme):
"""Calculs fiscaux pour une activité donnée.""" """Calculs fiscaux pour une activité donnée."""
label = dict(Activite.choices).get(activite, activite) label = dict(Activite.choices).get(activite, activite)
@@ -110,7 +136,6 @@ def synthese(entreprise, annee):
"""Tableau de bord chiffré, ventilé par activité puis totalisé.""" """Tableau de bord chiffré, ventilé par activité puis totalisé."""
ca_par_act = ca_ht_par_activite(entreprise, annee) ca_par_act = ca_ht_par_activite(entreprise, annee)
# On inclut toujours l'activité principale, même sans CA encore.
activites = set(ca_par_act) | {entreprise.activite} activites = set(ca_par_act) | {entreprise.activite}
baremes = {b.activite: b for b in Bareme.objects.filter(annee=annee)} baremes = {b.activite: b for b in Bareme.objects.filter(annee=annee)}
@@ -119,13 +144,14 @@ def synthese(entreprise, annee):
ca = ca_par_act.get(act, Decimal("0")) ca = ca_par_act.get(act, Decimal("0"))
lignes.append(detail_activite(act, ca, baremes.get(act))) lignes.append(detail_activite(act, ca, baremes.get(act)))
# Totaux (on additionne ce qui est calculable)
def somme(cle): def somme(cle):
vals = [l[cle] for l in lignes if l.get(cle) is not None] vals = [l[cle] for l in lignes if l.get(cle) is not None]
return _q(sum(vals, Decimal("0"))) if vals else None return _q(sum(vals, Decimal("0"))) if vals else None
tva_col = tva_collectee(entreprise, annee) tva_ventes = tva_sur_ventes(entreprise, annee)
tva_autoliq = tva_autoliquidee(entreprise, annee)
tva_ded = tva_deductible(entreprise, annee) tva_ded = tva_deductible(entreprise, annee)
tva_col = _q(tva_ventes + tva_autoliq)
return { return {
"entreprise": entreprise, "entreprise": entreprise,
@@ -137,6 +163,8 @@ def synthese(entreprise, annee):
"versement_liberatoire": ( "versement_liberatoire": (
somme("versement_liberatoire") if entreprise.versement_liberatoire else None), somme("versement_liberatoire") if entreprise.versement_liberatoire else None),
"revenu_imposable": somme("revenu_imposable"), "revenu_imposable": somme("revenu_imposable"),
"tva_sur_ventes": tva_ventes,
"tva_autoliquidee": tva_autoliq,
"tva_collectee": tva_col, "tva_collectee": tva_col,
"tva_deductible": tva_ded, "tva_deductible": tva_ded,
"tva_a_reverser": _q(tva_col - tva_ded), "tva_a_reverser": _q(tva_col - tva_ded),
+13
View File
@@ -90,6 +90,7 @@
<div class="label">TVA à reverser</div> <div class="label">TVA à reverser</div>
<div class="value">{{ tva_a_reverser }} €</div> <div class="value">{{ tva_a_reverser }} €</div>
<div class="hint">{{ tva_collectee }} collectée {{ tva_deductible }} déductible</div> <div class="hint">{{ tva_collectee }} collectée {{ tva_deductible }} déductible</div>
{% if tva_autoliquidee %}<div class="hint">dont {{ tva_autoliquidee }} € auto-liquidée (UE/import)</div>{% endif %}
</div> </div>
</div> </div>
@@ -128,6 +129,18 @@
</table> </table>
</div> </div>
<!-- Détail TVA -->
<h2>TVA</h2>
<div class="card">
<table>
<tr><td>TVA collectée sur ventes</td><td>{{ tva_sur_ventes }} €</td></tr>
<tr><td>TVA auto-liquidée (UE / import)</td><td>{{ tva_autoliquidee }} €</td></tr>
<tr><td>TVA collectée totale (due)</td><td>{{ tva_collectee }} €</td></tr>
<tr><td>TVA déductible</td><td>{{ tva_deductible }} €</td></tr>
<tr class="total"><td>TVA à reverser</td><td>{{ tva_a_reverser }} €</td></tr>
</table>
</div>
<div class="row"> <div class="row">
<div class="card"> <div class="card">
<div class="label" style="margin-bottom:8px;">CA HT mensuel (toutes activités)</div> <div class="label" style="margin-bottom:8px;">CA HT mensuel (toutes activités)</div>
+4 -3
View File
@@ -64,9 +64,10 @@ def export_tva(request):
s = services.synthese(entreprise, annee) s = services.synthese(entreprise, annee)
resp = _csv_response(f"tva_{annee}.csv") resp = _csv_response(f"tva_{annee}.csv")
w = csv.writer(resp, delimiter=";") w = csv.writer(resp, delimiter=";")
w.writerow(["Annee", "TVA collectee", "TVA deductible", "TVA a reverser"]) w.writerow(["Annee", "TVA sur ventes", "TVA autoliquidee (UE/import)",
w.writerow([annee, s["tva_collectee"], s["tva_deductible"], "TVA collectee totale", "TVA deductible", "TVA a reverser"])
s["tva_a_reverser"]]) w.writerow([annee, s["tva_sur_ventes"], s["tva_autoliquidee"],
s["tva_collectee"], s["tva_deductible"], s["tva_a_reverser"]])
return resp return resp