Autoliquidation TVA (achats UE/import)
This commit is contained in:
+9
-6
@@ -16,9 +16,9 @@ class BaremeAdmin(admin.ModelAdmin):
|
||||
@admin.register(Entreprise)
|
||||
class EntrepriseAdmin(admin.ModelAdmin):
|
||||
list_display = ("nom", "activite", "franchise_tva",
|
||||
"versement_liberatoire", "email")
|
||||
"versement_liberatoire", "num_tva_intracom", "email")
|
||||
list_filter = ("activite", "franchise_tva", "versement_liberatoire")
|
||||
search_fields = ("nom", "siret")
|
||||
search_fields = ("nom", "siret", "num_tva_intracom")
|
||||
|
||||
|
||||
@admin.register(Client)
|
||||
@@ -52,14 +52,17 @@ class FactureAdmin(admin.ModelAdmin):
|
||||
|
||||
def save_related(self, request, form, formsets, change):
|
||||
super().save_related(request, form, formsets, change)
|
||||
# Recalcule les totaux à partir des lignes après sauvegarde
|
||||
form.instance.recompute_totaux()
|
||||
|
||||
|
||||
@admin.register(Depense)
|
||||
class DepenseAdmin(admin.ModelAdmin):
|
||||
list_display = ("date", "entreprise", "libelle", "activite", "categorie",
|
||||
"montant_ttc", "montant_tva")
|
||||
list_filter = ("entreprise", "activite", "categorie", "date")
|
||||
list_display = ("date", "entreprise", "libelle", "activite", "regime_tva",
|
||||
"montant_ttc", "montant_tva", "tva_autoliq")
|
||||
list_filter = ("entreprise", "regime_tva", "activite", "categorie", "date")
|
||||
search_fields = ("libelle", "categorie")
|
||||
date_hierarchy = "date"
|
||||
|
||||
@admin.display(description="TVA auto-liquidée")
|
||||
def tva_autoliq(self, obj):
|
||||
return obj.tva_autoliquidee
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
from decimal import Decimal
|
||||
@@ -9,7 +9,7 @@ from django.core.management.base import BaseCommand
|
||||
|
||||
from compta.models import (
|
||||
Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Activite,
|
||||
StatutFacture,
|
||||
StatutFacture, RegimeTva,
|
||||
)
|
||||
|
||||
# Barèmes officiels 2026 (voir docs/regles-fiscales-2026.md)
|
||||
@@ -42,7 +42,8 @@ class Command(BaseCommand):
|
||||
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"),
|
||||
versement_liberatoire=True, email="jb@example.fr",
|
||||
num_tva_intracom="FR00123456789"),
|
||||
)
|
||||
|
||||
clients = {}
|
||||
@@ -89,5 +90,12 @@ class Command(BaseCommand):
|
||||
entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10),
|
||||
defaults=dict(categorie="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,
|
||||
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."))
|
||||
|
||||
@@ -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
@@ -24,6 +24,13 @@ class StatutFacture(models.TextChoices):
|
||||
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):
|
||||
"""Taux et seuils officiels, par activité et par année (paramétrable)."""
|
||||
activite = models.CharField(max_length=20, choices=Activite.choices)
|
||||
@@ -68,6 +75,8 @@ class Entreprise(models.Model):
|
||||
default=Decimal("20.00"))
|
||||
versement_liberatoire = models.BooleanField(
|
||||
"Option versement libératoire", default=False)
|
||||
num_tva_intracom = models.CharField(
|
||||
"N° TVA intracommunautaire", max_length=20, blank=True)
|
||||
email = models.EmailField(blank=True)
|
||||
adresse = models.TextField(blank=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).")
|
||||
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_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(
|
||||
"TVA déductible (€)", max_digits=12, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
"TVA déductible facturée (€)", max_digits=12, decimal_places=2,
|
||||
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)
|
||||
|
||||
class Meta:
|
||||
@@ -194,6 +213,29 @@ class Depense(models.Model):
|
||||
def __str__(self):
|
||||
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
|
||||
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"))
|
||||
|
||||
@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
@@ -6,10 +6,14 @@ Bases de calcul : CA HORS TAXES (HT) encaissé.
|
||||
- En franchise de TVA, HT = TTC : le calcul reste correct.
|
||||
|
||||
Multi-activité : une entreprise peut exercer plusieurs activités (ex. Vente BIC
|
||||
+ Prestation BIC). Les taux URSSAF, abattements, taux de versement libératoire et
|
||||
seuils diffèrent par activité, donc tout est ventilé par activité puis totalisé.
|
||||
+ Prestation BIC). Taux URSSAF, abattements, taux VL et seuils diffèrent par
|
||||
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 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):
|
||||
"""Dict {activite: CA HT encaissé}."""
|
||||
rows = (
|
||||
@@ -47,18 +55,34 @@ def ca_ht(entreprise, annee):
|
||||
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"))
|
||||
return _q(agg["s"])
|
||||
|
||||
|
||||
def tva_deductible(entreprise, annee):
|
||||
agg = Depense.objects.filter(
|
||||
entreprise=entreprise, date__year=annee
|
||||
).aggregate(s=Sum("montant_tva"))
|
||||
return _q(agg["s"])
|
||||
def tva_autoliquidee(entreprise, annee):
|
||||
"""TVA auto-liquidée sur les achats UE/import (intracom + import)."""
|
||||
total = sum((d.tva_autoliquidee for d in depenses_annee(entreprise, annee)),
|
||||
Decimal("0"))
|
||||
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):
|
||||
"""Liste de 12 valeurs (index 0 = janvier) du CA HT encaissé (toutes activités)."""
|
||||
rows = (
|
||||
@@ -74,6 +98,8 @@ def ca_ht_mensuel(entreprise, annee):
|
||||
return mensuel
|
||||
|
||||
|
||||
# ---- Détail par activité ------------------------------------------------
|
||||
|
||||
def detail_activite(activite, ca, bareme):
|
||||
"""Calculs fiscaux pour une activité donnée."""
|
||||
label = dict(Activite.choices).get(activite, activite)
|
||||
@@ -110,7 +136,6 @@ def synthese(entreprise, annee):
|
||||
"""Tableau de bord chiffré, ventilé par activité puis totalisé."""
|
||||
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}
|
||||
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"))
|
||||
lignes.append(detail_activite(act, ca, baremes.get(act)))
|
||||
|
||||
# Totaux (on additionne ce qui est calculable)
|
||||
def somme(cle):
|
||||
vals = [l[cle] for l in lignes if l.get(cle) is not 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_col = _q(tva_ventes + tva_autoliq)
|
||||
|
||||
return {
|
||||
"entreprise": entreprise,
|
||||
@@ -137,6 +163,8 @@ def synthese(entreprise, annee):
|
||||
"versement_liberatoire": (
|
||||
somme("versement_liberatoire") if entreprise.versement_liberatoire else None),
|
||||
"revenu_imposable": somme("revenu_imposable"),
|
||||
"tva_sur_ventes": tva_ventes,
|
||||
"tva_autoliquidee": tva_autoliq,
|
||||
"tva_collectee": tva_col,
|
||||
"tva_deductible": tva_ded,
|
||||
"tva_a_reverser": _q(tva_col - tva_ded),
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<div class="label">TVA à reverser</div>
|
||||
<div class="value">{{ tva_a_reverser }} €</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>
|
||||
|
||||
@@ -128,6 +129,18 @@
|
||||
</table>
|
||||
</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="card">
|
||||
<div class="label" style="margin-bottom:8px;">CA HT mensuel (toutes activités)</div>
|
||||
|
||||
+4
-3
@@ -64,9 +64,10 @@ def export_tva(request):
|
||||
s = services.synthese(entreprise, annee)
|
||||
resp = _csv_response(f"tva_{annee}.csv")
|
||||
w = csv.writer(resp, delimiter=";")
|
||||
w.writerow(["Annee", "TVA collectee", "TVA deductible", "TVA a reverser"])
|
||||
w.writerow([annee, s["tva_collectee"], s["tva_deductible"],
|
||||
s["tva_a_reverser"]])
|
||||
w.writerow(["Annee", "TVA sur ventes", "TVA autoliquidee (UE/import)",
|
||||
"TVA collectee totale", "TVA deductible", "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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user