diff --git a/compta/migrations/0012_entreprise_periodicite_tva_and_more.py b/compta/migrations/0012_entreprise_periodicite_tva_and_more.py
new file mode 100644
index 0000000..60af093
--- /dev/null
+++ b/compta/migrations/0012_entreprise_periodicite_tva_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.15 on 2026-07-15 09:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compta', '0011_bareme_taux_cfp'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='entreprise',
+ name='periodicite_tva',
+ field=models.CharField(choices=[('mensuelle', 'Mensuelle'), ('trimestrielle', 'Trimestrielle')], default='mensuelle', max_length=13, verbose_name='Périodicité déclaration TVA'),
+ ),
+ migrations.AddField(
+ model_name='entreprise',
+ name='periodicite_urssaf',
+ field=models.CharField(choices=[('mensuelle', 'Mensuelle'), ('trimestrielle', 'Trimestrielle')], default='mensuelle', max_length=13, verbose_name='Périodicité déclaration URSSAF'),
+ ),
+ ]
diff --git a/compta/models.py b/compta/models.py
index 5c30dd5..8e87ca6 100644
--- a/compta/models.py
+++ b/compta/models.py
@@ -82,6 +82,7 @@ class Bareme(models.Model):
class Entreprise(models.Model):
VUE_TVA = [("simplifie", "Résumé simplifié"), ("ca3", "Champs CA3 officiels")]
+ PERIODICITE = [("mensuelle", "Mensuelle"), ("trimestrielle", "Trimestrielle")]
nom = models.CharField(max_length=200)
siret = models.CharField(max_length=20, blank=True)
activite = models.CharField(
@@ -95,6 +96,10 @@ class Entreprise(models.Model):
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")
+ periodicite_urssaf = models.CharField(
+ "Périodicité déclaration URSSAF", max_length=13, choices=PERIODICITE, default="mensuelle")
+ periodicite_tva = models.CharField(
+ "Périodicité déclaration TVA", max_length=13, choices=PERIODICITE, default="mensuelle")
indy_token = models.TextField(
"Token Indy (API)", blank=True,
help_text="Token JWT capté dans le navigateur, pour la synchronisation. Expire ~1 h.")
diff --git a/compta/services.py b/compta/services.py
index ac67bc7..ffef0f9 100644
--- a/compta/services.py
+++ b/compta/services.py
@@ -33,19 +33,24 @@ def _somme_euro(lignes, cle):
# ---- Sélections de période ---------------------------------------------
+def _filtre_mois(qs, field, mois):
+ """mois peut être un entier (1 mois), une liste de mois (trimestre) ou None (année)."""
+ if not mois:
+ return qs
+ if isinstance(mois, (list, tuple, set)):
+ return qs.filter(**{f"{field}__month__in": list(mois)})
+ return qs.filter(**{f"{field}__month": mois})
+
+
def factures_encaissees(entreprise, annee, mois=None):
qs = Facture.objects.filter(entreprise=entreprise, statut=StatutFacture.ENCAISSEE,
date_encaissement__year=annee)
- if mois:
- qs = qs.filter(date_encaissement__month=mois)
- return qs
+ return _filtre_mois(qs, "date_encaissement", mois)
def depenses_periode(entreprise, annee, mois=None):
qs = Depense.objects.filter(entreprise=entreprise, date__year=annee)
- if mois:
- qs = qs.filter(date__month=mois)
- return qs
+ return _filtre_mois(qs, "date", mois)
# ---- CA -----------------------------------------------------------------
@@ -80,6 +85,22 @@ def tva_deductible(entreprise, annee, mois=None):
return _q(total)
+def tva_deductible_split(entreprise, annee, mois=None):
+ """TVA déductible ventilée : (immobilisations [ligne 19], autres biens/services [ligne 20])."""
+ immo = Decimal("0")
+ autres = Decimal("0")
+ for d in depenses_periode(entreprise, annee, mois):
+ v = d.tva_deductible_value
+ if not v:
+ continue
+ compte = (d.categorie.compte_pcg if d.categorie else "") or ""
+ if compte.startswith("2"): # comptes de classe 2 = immobilisations
+ immo += v
+ else:
+ autres += v
+ return _q(immo), _q(autres)
+
+
def tva_collectee(entreprise, annee, mois=None):
return _q(tva_sur_ventes(entreprise, annee, mois)
+ tva_autoliquidee(entreprise, annee, mois))
@@ -217,14 +238,14 @@ def tableau_mensuel(entreprise, annee):
# ---- Déclaration URSSAF (période) --------------------------------------
-def declaration_urssaf(entreprise, annee, mois=None):
+def declaration_urssaf(entreprise, annee, mois=None, label=None):
lignes = _lignes_activite(entreprise, annee, mois)
# URSSAF : le CA déclaré et les cotisations sont arrondis à l'euro le plus proche.
for l in lignes:
l["ca_ht"] = _euro(l["ca_ht"])
return {
"entreprise": entreprise, "annee": annee, "mois": mois,
- "mois_nom": MOIS_FR[mois - 1] if mois else "Année entière",
+ "mois_nom": label or (MOIS_FR[mois - 1] if isinstance(mois, int) else "Année entière"),
"lignes": lignes,
"ca_ht": _euro(ca_ht(entreprise, annee, mois)),
"cotisations": _somme_euro(lignes, "cotisations"),
@@ -241,49 +262,86 @@ def declaration_urssaf(entreprise, annee, mois=None):
# ---- Déclaration TVA (période) -----------------------------------------
-def declaration_tva(entreprise, annee, mois=None):
+def _ca3_ligne(ref, libelle, base=None, tva=None, total=False):
+ return {"ref": ref, "libelle": libelle, "base": base, "tva": tva, "total": total}
+
+
+def declaration_tva(entreprise, annee, mois=None, label=None):
par_taux = tva_par_taux(entreprise, annee, mois)
intra_base, intra_tva = intracom_periode(entreprise, annee, mois)
- tva_ventes = sum((t for _, t in par_taux.values()), Decimal("0"))
- tva_ded = tva_deductible(entreprise, annee, mois)
- # CA3 : TVA collectée, déductible et à payer arrondies à l'euro le plus proche.
- tva_brute = _euro(tva_ventes + intra_tva)
- tva_ded = _euro(tva_ded)
- a_payer = tva_brute - tva_ded
-
+ # CA3 : chaque ligne (base ET TVA) est arrondie à l'euro le plus proche ;
+ # les totaux sont la somme des lignes arrondies (comme le formulaire officiel).
def couple(taux):
- return par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00")))
+ b, t = par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00")))
+ return _euro(b), _euro(t)
b20, t20 = couple("20.00")
b10, t10 = couple("10.00")
b55, t55 = couple("5.50")
- base_taxable = _q(sum((b for b, _ in par_taux.values()), Decimal("0")))
+ intra_base = _euro(intra_base)
+ intra_tva = _euro(intra_tva)
+ ded_immo, ded_autres = tva_deductible_split(entreprise, annee, mois)
+ ded_immo, ded_autres = _euro(ded_immo), _euro(ded_autres)
+ tva_ventes = t20 + t10 + t55
+ tva_brute = tva_ventes + intra_tva
+ tva_ded = ded_immo + ded_autres
+ a_payer = tva_brute - tva_ded
+ base_taxable = b20 + b10 + b55
+
+ Z = Decimal("0")
+ # --- Cadre A : montant des opérations réalisées (bases HT) ---
+ a_taxees = []
+ if base_taxable:
+ a_taxees.append(_ca3_ligne("A1", "Ventes, prestations de services", base=base_taxable))
+ if intra_base:
+ a_taxees.append(_ca3_ligne("B2", "Acquisitions intracommunautaires", base=intra_base))
+ # --- Cadre B : décompte de la TVA à payer ---
+ brute = []
+ if b20 or t20:
+ brute.append(_ca3_ligne("08", "Taux normal 20 %", base=b20, tva=t20))
+ if b10 or t10:
+ brute.append(_ca3_ligne("9B", "Taux réduit 10 %", base=b10, tva=t10))
+ if b55 or t55:
+ brute.append(_ca3_ligne("09", "Taux réduit 5,5 %", base=b55, tva=t55))
+ if intra_tva:
+ brute.append(_ca3_ligne("17", "Dont TVA sur acquisitions intracommunautaires", tva=intra_tva))
+ brute.append(_ca3_ligne("16", "Total de la TVA brute due", tva=tva_brute, total=True))
+
+ deductible = []
+ if ded_immo:
+ deductible.append(_ca3_ligne("19", "Biens constituant des immobilisations", tva=ded_immo))
+ deductible.append(_ca3_ligne("20", "Autres biens et services", tva=ded_autres))
+ deductible.append(_ca3_ligne("23", "Total de la TVA déductible", tva=tva_ded, total=True))
+
+ if a_payer >= 0:
+ solde = {"titre": "Taxe due",
+ "lignes": [_ca3_ligne("TD", "TVA due (ligne 16 − ligne 23)", tva=a_payer, total=True)]}
+ else:
+ solde = {"titre": "Crédit",
+ "lignes": [_ca3_ligne("25", "Crédit de TVA (ligne 23 − ligne 16)",
+ tva=_euro(-a_payer), total=True)]}
+
+ cadres = [
+ {"titre": "A — Montant des opérations réalisées",
+ "sous": [{"titre": "Opérations taxées (HT)", "lignes": a_taxees}] if a_taxees else []},
+ {"titre": "B — Décompte de la TVA à payer",
+ "sous": [{"titre": "TVA brute", "lignes": brute},
+ {"titre": "TVA déductible", "lignes": deductible},
+ solde]},
+ ]
return {
"entreprise": entreprise, "annee": annee, "mois": mois,
- "mois_nom": MOIS_FR[mois - 1] if mois else "Année entière",
- "par_taux": [{"taux": k, "base": v[0], "tva": v[1]} for k, v in par_taux.items()],
- # Résumé simplifié
- "tva_sur_ventes": _q(tva_ventes),
+ "mois_nom": label or (MOIS_FR[mois - 1] if isinstance(mois, int) else "Année entière"),
+ "par_taux": [{"taux": k, "base": _euro(v[0]), "tva": _euro(v[1])}
+ for k, v in par_taux.items()],
+ "tva_sur_ventes": tva_ventes,
"tva_autoliquidee": intra_tva,
"tva_collectee": tva_brute,
"tva_deductible": tva_ded,
- "tva_a_payer": a_payer,
- "credit_tva": _q(-a_payer) if a_payer < 0 else Decimal("0.00"),
- # Champs CA3
- "ca3": {
- "l01_base_taxable": base_taxable,
- "l08_base": b20, "l08_tva": t20,
- "l9B_base": b10, "l9B_tva": t10,
- "l09_base": b55, "l09_tva": t55,
- "l03_intra_base": intra_base,
- "l17_intra_tva": intra_tva,
- "l16_tva_brute": tva_brute,
- "l20_tva_deductible": tva_ded,
- "l28_total_deductible": tva_ded,
- "l32_a_payer": a_payer if a_payer > 0 else Decimal("0.00"),
- "l25_credit": _q(-a_payer) if a_payer < 0 else Decimal("0.00"),
- },
+ "tva_a_payer": a_payer if a_payer > 0 else Z,
+ "credit_tva": _euro(-a_payer) if a_payer < 0 else Z,
+ "cadres": cadres,
}
diff --git a/compta/templates/compta/decl_tva.html b/compta/templates/compta/decl_tva.html
index 96189d2..ef313c9 100644
--- a/compta/templates/compta/decl_tva.html
+++ b/compta/templates/compta/decl_tva.html
@@ -2,45 +2,51 @@
{% block titre %}Déclaration TVA{% endblock %}
{% block extrafilters %}
-
+
{% endblock %}
{% block content %}
Déclaration TVA — {{ decl.mois_nom }} {{ annee }}
Mode :
- Résumé ·
- Champs CA3
- (réglage par défaut de l'entreprise : {{ entreprise.get_vue_tva_display }})
+ (réglage par défaut : {{ entreprise.get_vue_tva_display }})
{% if mode == 'ca3' %}
-
-
Ligne CA3
Libellé
Base HT
TVA
-
-
01
Ventes / prestations taxables
{{ decl.ca3.l01_base_taxable }} €
-
08
Taux normal 20 %
{{ decl.ca3.l08_base }} €
{{ decl.ca3.l08_tva }} €
-
9B
Taux réduit 10 %
{{ decl.ca3.l9B_base }} €
{{ decl.ca3.l9B_tva }} €
-
09
Taux réduit 5,5 %
{{ decl.ca3.l09_base }} €
{{ decl.ca3.l09_tva }} €
-
03
Acquisitions intracommunautaires
{{ decl.ca3.l03_intra_base }} €
-
17
dont TVA sur acquisitions intracom
{{ decl.ca3.l17_intra_tva }} €
-
16
TVA brute due
{{ decl.ca3.l16_tva_brute }} €
-
20
TVA déductible (biens et services)
{{ decl.ca3.l20_tva_deductible }} €
-
28
Total TVA déductible
{{ decl.ca3.l28_total_deductible }} €
-
32
TVA à payer
{{ decl.ca3.l32_a_payer }} €
- {% if decl.ca3.l25_credit %}
25
Crédit de TVA
{{ decl.ca3.l25_credit }} €
{% endif %}
-
-
+ {% for cadre in decl.cadres %}
+ {% if cadre.sous %}
+
Cadre {{ cadre.titre }}
+ {% for sous in cadre.sous %}
+
{{ sous.titre }}
+
+
Ligne
Libellé
Base HT
TVA
+
+ {% for l in sous.lignes %}
+
+
{{ l.ref }}
+
{{ l.libelle }}
+
{% if l.base is not None %}{{ l.base }} €{% endif %}
+
{% if l.tva is not None %}{{ l.tva }} €{% endif %}
+
+ {% endfor %}
+
+
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
-
Numéros de lignes du formulaire 3310-CA3 (régime réel normal). À recouper
- avec ta déclaration officielle — vérifie notamment la ventilation par taux.
+
Numéros de lignes du formulaire 3310-CA3 (régime réel normal).
+ Seules les lignes utilisées sont affichées. Montants arrondis à l'euro, comme le formulaire.