From cd5605819a088334b1db8c003d5407067b4f1236 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 15 Jul 2026 11:32:19 +0200 Subject: [PATCH] Arrondi URSSAF/TVA/VFL a l'euro + CFP + fix montant popup + fix categorie favori --- compta/admin.py | 2 +- compta/management/commands/seed_reference.py | 12 +++-- compta/migrations/0011_bareme_taux_cfp.py | 19 +++++++ compta/models.py | 4 ++ compta/services.py | 55 ++++++++++++++------ compta/templates/compta/bookmarklet.html | 2 +- compta/templates/compta/decl_urssaf.html | 7 +++ compta/templates/compta/gestion.html | 2 +- compta/views_import.py | 5 +- 9 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 compta/migrations/0011_bareme_taux_cfp.py diff --git a/compta/admin.py b/compta/admin.py index d8b21f4..d76b55c 100644 --- a/compta/admin.py +++ b/compta/admin.py @@ -39,7 +39,7 @@ class CategorieAdmin(admin.ModelAdmin): @admin.register(Bareme) class BaremeAdmin(admin.ModelAdmin): list_display = ("annee", "activite", "taux_urssaf", "abattement_fiscal", - "taux_vl", "seuil_ca", "seuil_tva_base") + "taux_vl", "taux_cfp", "seuil_ca", "seuil_tva_base") list_filter = ("annee", "activite") diff --git a/compta/management/commands/seed_reference.py b/compta/management/commands/seed_reference.py index 46747b6..7cf1bb4 100644 --- a/compta/management/commands/seed_reference.py +++ b/compta/management/commands/seed_reference.py @@ -12,13 +12,14 @@ from django.contrib.auth.models import Group, Permission from compta.models import Bareme, Categorie, Activite from compta.categories_indy import CATALOGUE +# activite, taux_urssaf, abattement, taux_vl, seuil_ca, seuil_tva_base, seuil_tva_majore, taux_cfp BAREMES_2026 = [ (Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"), - Decimal("203100"), Decimal("85000"), Decimal("93500")), + Decimal("203100"), Decimal("85000"), Decimal("93500"), Decimal("0.10")), (Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"), - Decimal("83600"), Decimal("37500"), Decimal("41250")), + Decimal("83600"), Decimal("37500"), Decimal("41250"), Decimal("0.30")), (Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"), - Decimal("83600"), Decimal("37500"), Decimal("41250")), + Decimal("83600"), Decimal("37500"), Decimal("41250"), Decimal("0.20")), ] GROUPE_NOM = "Gestionnaire entreprise" @@ -46,11 +47,12 @@ def charger_groupes(): def charger_reference(stdout=None, style=None): - for act, urssaf, ab, vl, sca, tb, tm in BAREMES_2026: + for act, urssaf, ab, vl, sca, tb, tm, cfp 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)) + seuil_ca=sca, seuil_tva_base=tb, seuil_tva_majore=tm, + taux_cfp=cfp)) cat = {} for i, (nom, usage, excl, compte) in enumerate(CATALOGUE): c, _ = Categorie.objects.update_or_create( diff --git a/compta/migrations/0011_bareme_taux_cfp.py b/compta/migrations/0011_bareme_taux_cfp.py new file mode 100644 index 0000000..b249e6e --- /dev/null +++ b/compta/migrations/0011_bareme_taux_cfp.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.15 on 2026-07-15 09:28 + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compta', '0010_categorie_compte_pcg'), + ] + + operations = [ + migrations.AddField( + model_name='bareme', + name='taux_cfp', + field=models.DecimalField(decimal_places=2, default=Decimal('0'), help_text='Contribution à la formation professionnelle : vente 0,1 % · artisan 0,3 % · service/libéral 0,2 %.', max_digits=4, verbose_name='Taux CFP (%)'), + ), + ] diff --git a/compta/models.py b/compta/models.py index 3ba769e..5c30dd5 100644 --- a/compta/models.py +++ b/compta/models.py @@ -62,6 +62,10 @@ class Bareme(models.Model): taux_urssaf = models.DecimalField("Taux cotisations URSSAF (%)", max_digits=5, decimal_places=2) abattement_fiscal = models.DecimalField("Abattement forfaitaire (%)", max_digits=5, decimal_places=2) taux_vl = models.DecimalField("Taux versement libératoire (%)", max_digits=4, decimal_places=2) + taux_cfp = models.DecimalField( + "Taux CFP (%)", max_digits=4, decimal_places=2, default=Decimal("0"), + help_text="Contribution à la formation professionnelle : vente 0,1 % · " + "artisan 0,3 % · service/libéral 0,2 %.") seuil_ca = models.DecimalField("Plafond CA micro (€)", max_digits=12, decimal_places=2) seuil_tva_base = models.DecimalField("Seuil franchise TVA (€)", max_digits=12, decimal_places=2) seuil_tva_majore = models.DecimalField("Seuil franchise TVA majoré (€)", max_digits=12, decimal_places=2) diff --git a/compta/services.py b/compta/services.py index ebbb7e1..ac67bc7 100644 --- a/compta/services.py +++ b/compta/services.py @@ -5,7 +5,7 @@ Bases : CA HORS TAXES (HT) encaissé. TVA collectée = TVA ventes + TVA auto-liquidée (UE/import). TVA déductible = TVA achats nationaux + TVA auto-liquidée. Neutres exclus. """ -from decimal import Decimal +from decimal import Decimal, ROUND_HALF_UP from django.db.models import Sum from django.db.models.functions import TruncMonth @@ -21,6 +21,16 @@ def _q(v): return (v or Decimal("0")).quantize(CENT) +def _euro(v): + """Arrondi à l'euro le plus proche (règle URSSAF / TVA : 0,50 → euro sup.).""" + return (v or Decimal("0")).quantize(Decimal("1"), rounding=ROUND_HALF_UP) + + +def _somme_euro(lignes, cle): + vals = [l[cle] for l in lignes if l.get(cle) is not None] + return _euro(sum(vals, Decimal("0"))) if vals else None + + # ---- Sélections de période --------------------------------------------- def factures_encaissees(entreprise, annee, mois=None): @@ -120,9 +130,11 @@ def detail_activite(activite, ca, bareme): if bareme: d.update({ "taux_urssaf": bareme.taux_urssaf, - "cotisations": _q(ca * bareme.taux_urssaf / Decimal("100")), + "cotisations": _euro(ca * bareme.taux_urssaf / Decimal("100")), "taux_vl": bareme.taux_vl, - "versement_liberatoire": _q(ca * bareme.taux_vl / Decimal("100")), + "versement_liberatoire": _euro(ca * bareme.taux_vl / Decimal("100")), + "taux_cfp": bareme.taux_cfp, + "cfp": _euro(ca * bareme.taux_cfp / Decimal("100")), "abattement_fiscal": bareme.abattement_fiscal, "revenu_imposable": _q(ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")), "seuil_ca": bareme.seuil_ca, @@ -133,8 +145,8 @@ def detail_activite(activite, ca, bareme): }) else: for k in ("taux_urssaf", "cotisations", "taux_vl", "versement_liberatoire", - "abattement_fiscal", "revenu_imposable", "seuil_ca", "pct_seuil_ca", - "seuil_tva_base"): + "taux_cfp", "cfp", "abattement_fiscal", "revenu_imposable", + "seuil_ca", "pct_seuil_ca", "seuil_tva_base"): d[k] = None d["depasse_seuil_tva"] = d["depasse_seuil_ca"] = False return d @@ -165,13 +177,13 @@ def synthese(entreprise, annee): "entreprise": entreprise, "annee": annee, "lignes_activite": lignes, "bareme_manquant": any(l["bareme"] is None and l["ca_ht"] for l in lignes), "ca_ht": ca_ht(entreprise, annee), - "cotisations_urssaf": _somme(lignes, "cotisations"), - "versement_liberatoire": (_somme(lignes, "versement_liberatoire") + "cotisations_urssaf": _somme_euro(lignes, "cotisations"), + "versement_liberatoire": (_somme_euro(lignes, "versement_liberatoire") if entreprise.versement_liberatoire else None), "revenu_imposable": _somme(lignes, "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), + "tva_a_reverser": _euro(_euro(tva_col) - _euro(tva_ded)), "ca_mensuel": ca_ht_mensuel(entreprise, annee), } @@ -195,10 +207,10 @@ def tableau_mensuel(entreprise, annee): rows.append({ "mois": m, "nom": MOIS_FR[m - 1], "ca_ht": ca_ht(entreprise, annee, m), - "cotisations": _q(cot), - "versement_liberatoire": _q(vl) if entreprise.versement_liberatoire else None, + "cotisations": _euro(cot), + "versement_liberatoire": _euro(vl) if entreprise.versement_liberatoire else None, "tva_collectee": tva_col, "tva_deductible": tva_ded, - "tva_a_reverser": _q(tva_col - tva_ded), + "tva_a_reverser": _euro(_euro(tva_col) - _euro(tva_ded)), }) return rows @@ -207,14 +219,23 @@ def tableau_mensuel(entreprise, annee): def declaration_urssaf(entreprise, annee, mois=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", "lignes": lignes, - "ca_ht": ca_ht(entreprise, annee, mois), - "cotisations": _somme(lignes, "cotisations"), - "versement_liberatoire": (_somme(lignes, "versement_liberatoire") + "ca_ht": _euro(ca_ht(entreprise, annee, mois)), + "cotisations": _somme_euro(lignes, "cotisations"), + "cfp": _somme_euro(lignes, "cfp"), + "versement_liberatoire": (_somme_euro(lignes, "versement_liberatoire") if entreprise.versement_liberatoire else None), + "total_regle": _euro( + (_somme_euro(lignes, "cotisations") or Decimal("0")) + + (_somme_euro(lignes, "cfp") or Decimal("0")) + + ((_somme_euro(lignes, "versement_liberatoire") or Decimal("0")) + if entreprise.versement_liberatoire else Decimal("0"))), } @@ -225,8 +246,10 @@ def declaration_tva(entreprise, annee, mois=None): 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) - tva_brute = _q(tva_ventes + intra_tva) - a_payer = _q(tva_brute - tva_ded) + # 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 def couple(taux): return par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00"))) diff --git a/compta/templates/compta/bookmarklet.html b/compta/templates/compta/bookmarklet.html index e2f2129..6245231 100644 --- a/compta/templates/compta/bookmarklet.html +++ b/compta/templates/compta/bookmarklet.html @@ -51,7 +51,7 @@ {% verbatim %} diff --git a/compta/templates/compta/decl_urssaf.html b/compta/templates/compta/decl_urssaf.html index ef53586..1689ac1 100644 --- a/compta/templates/compta/decl_urssaf.html +++ b/compta/templates/compta/decl_urssaf.html @@ -18,6 +18,7 @@ + {% if decl.versement_liberatoire is not None %}{% endif %} @@ -27,6 +28,7 @@ + {% if decl.versement_liberatoire is not None %} @@ -35,8 +37,13 @@ {% endfor %} + {% if decl.versement_liberatoire is not None %}{% endif %} + + + +
ActivitéCA HT à déclarerTaux URSSAFCotisationsCFPTaux VLImpôt (VL)
{{ l.ca_ht }} € {% if l.taux_urssaf is not None %}{{ l.taux_urssaf }} %{% else %}—{% endif %} {% if l.cotisations is not None %}{{ l.cotisations }} €{% else %}—{% endif %}{% if l.cfp is not None %}{{ l.cfp }} €{% else %}—{% endif %}{% if l.taux_vl is not None %}{{ l.taux_vl }} %{% else %}—{% endif %} {% if l.versement_liberatoire is not None %}{{ l.versement_liberatoire }} €{% else %}—{% endif %}
Total{{ decl.ca_ht }} €{{ decl.cotisations|default:"—" }} €{{ decl.cfp|default:"—" }} €{{ decl.versement_liberatoire }} €
Total cotisations et contributions à régler{{ decl.total_regle|default:"—" }} €
diff --git a/compta/templates/compta/gestion.html b/compta/templates/compta/gestion.html index 6f50948..43e81d6 100644 --- a/compta/templates/compta/gestion.html +++ b/compta/templates/compta/gestion.html @@ -115,7 +115,7 @@ data-type="{{ r.type }}" data-id="{{ r.id }}" data-libelle="{{ r.libelle }}" data-date="{{ r.date|date:'Y-m-d' }}" - data-montant="{{ r.montant|floatformat:2|unlocalize }}" + data-montant="{{ r.montant|unlocalize }}" data-cat="{{ r.categorie_id }}" data-activite="{{ r.activite }}" data-regime="{{ r.regime }}" diff --git a/compta/views_import.py b/compta/views_import.py index d6ae060..5d5d9d9 100644 --- a/compta/views_import.py +++ b/compta/views_import.py @@ -99,6 +99,8 @@ def _parse_csv(text, cats): delim = ";" if text.count(";") >= text.count(",") else "," rows = list(csv.reader(io.StringIO(text), delimiter=delim)) lignes, erreurs = [], [] + # Résolution par numéro de compte PCG (favori Indy) OU par nom (CSV manuel). + compte_map = {c.compte_pcg: c for c in Categorie.objects.all() if c.compte_pcg} start = 1 if rows and rows[0] and rows[0][0].strip().lower() == "date" else 0 for n, raw in enumerate(rows[start:], start=start + 1): if not any(c.strip() for c in raw): @@ -110,7 +112,8 @@ def _parse_csv(text, cats): erreurs.append(f"Ligne {n} : date invalide « {c[0]} »."); continue if montant == 0: erreurs.append(f"Ligne {n} : montant nul."); continue - cat = cats.get((c[2] or "").strip().lower()) + val = (c[2] or "").strip() + cat = compte_map.get(val) or cats.get(val.lower()) grp = (c[7] or "").strip() # colonne « groupe » (ventilation), optionnelle lignes.append(_row(dt, (c[1] or "").strip() or "(sans libellé)", montant, _dec(c[4]), _regime(c[5]), cat, _activite(c[3]),