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' %}
- - - - - - - - - - - - - - {% if decl.ca3.l25_credit %}{% endif %} - -
Ligne CA3LibelléBase HTTVA
01Ventes / prestations taxables{{ decl.ca3.l01_base_taxable }} €
08Taux normal 20 %{{ decl.ca3.l08_base }} €{{ decl.ca3.l08_tva }} €
9BTaux réduit 10 %{{ decl.ca3.l9B_base }} €{{ decl.ca3.l9B_tva }} €
09Taux réduit 5,5 %{{ decl.ca3.l09_base }} €{{ decl.ca3.l09_tva }} €
03Acquisitions intracommunautaires{{ decl.ca3.l03_intra_base }} €
17dont TVA sur acquisitions intracom{{ decl.ca3.l17_intra_tva }} €
16TVA brute due{{ decl.ca3.l16_tva_brute }} €
20TVA déductible (biens et services){{ decl.ca3.l20_tva_deductible }} €
28Total TVA déductible{{ decl.ca3.l28_total_deductible }} €
32TVA à payer{{ decl.ca3.l32_a_payer }} €
25Crédit de TVA{{ decl.ca3.l25_credit }} €
+ {% for cadre in decl.cadres %} + {% if cadre.sous %} +

Cadre {{ cadre.titre }}

+ {% for sous in cadre.sous %} +
{{ sous.titre }}
+ + + + {% for l in sous.lignes %} + + + + + + + {% endfor %} + +
LigneLibelléBase HTTVA
{{ l.ref }}{{ l.libelle }}{% if l.base is not None %}{{ l.base }} €{% endif %}{% if l.tva is not None %}{{ l.tva }} €{% endif %}
+ {% 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.
{% else %}
TVA collectée sur ventes
{{ decl.tva_sur_ventes }} €
diff --git a/compta/templates/compta/decl_urssaf.html b/compta/templates/compta/decl_urssaf.html index 1689ac1..0a1c89f 100644 --- a/compta/templates/compta/decl_urssaf.html +++ b/compta/templates/compta/decl_urssaf.html @@ -2,11 +2,12 @@ {% block titre %}Déclaration URSSAF{% endblock %} {% block extrafilters %} - + {% endblock %} {% block content %} diff --git a/compta/views.py b/compta/views.py index 2a24cac..f670638 100644 --- a/compta/views.py +++ b/compta/views.py @@ -24,6 +24,23 @@ def _annee(request): return int(request.GET.get("annee", date.today().year)) +TRIMESTRES = [(1, "T1 (janv.–mars)", [1, 2, 3]), (2, "T2 (avr.–juin)", [4, 5, 6]), + (3, "T3 (juil.–sept.)", [7, 8, 9]), (4, "T4 (oct.–déc.)", [10, 11, 12])] + + +def _periode(request, entreprise, champ): + """Retourne (mois_filtre, periode_sel, label, options, type) selon la + périodicité (mensuelle/trimestrielle) configurée sur l'entreprise.""" + if getattr(entreprise, champ, "mensuelle") == "trimestrielle": + defaut = (date.today().month - 1) // 3 + 1 + sel = min(max(int(request.GET.get("periode", defaut)), 1), 4) + _, label, mois = TRIMESTRES[sel - 1] + return mois, sel, label, [(i, lab) for i, lab, _ in TRIMESTRES], "trimestre" + defaut = date.today().month + sel = min(max(int(request.GET.get("periode", request.GET.get("mois", defaut))), 1), 12) + return sel, sel, MOIS[sel - 1], [(i, MOIS[i - 1]) for i in range(1, 13)], "mois" + + def _base_ctx(request, entreprise, annee, vue): return { "entreprise": entreprise, @@ -68,10 +85,10 @@ def declaration_urssaf(request): if entreprise is None: return render(request, "compta/decl_urssaf.html", {"entreprise": None}) annee = _annee(request) - mois = int(request.GET.get("mois", date.today().month)) + mois, periode_sel, label, options, ptype = _periode(request, entreprise, "periodicite_urssaf") ctx = _base_ctx(request, entreprise, annee, "urssaf") - ctx["mois_sel"] = mois - ctx["decl"] = services.declaration_urssaf(entreprise, annee, mois) + ctx.update({"periode_sel": periode_sel, "periode_options": options, "periode_type": ptype}) + ctx["decl"] = services.declaration_urssaf(entreprise, annee, mois, label) return render(request, "compta/decl_urssaf.html", ctx) @@ -81,12 +98,12 @@ def declaration_tva(request): if entreprise is None: return render(request, "compta/decl_tva.html", {"entreprise": None}) annee = _annee(request) - mois = int(request.GET.get("mois", date.today().month)) + mois, periode_sel, label, options, ptype = _periode(request, entreprise, "periodicite_tva") mode = request.GET.get("mode") or entreprise.vue_tva ctx = _base_ctx(request, entreprise, annee, "tva") - ctx["mois_sel"] = mois - ctx["mode"] = mode - ctx["decl"] = services.declaration_tva(entreprise, annee, mois) + ctx.update({"periode_sel": periode_sel, "periode_options": options, + "periode_type": ptype, "mode": mode}) + ctx["decl"] = services.declaration_tva(entreprise, annee, mois, label) return render(request, "compta/decl_tva.html", ctx)