Saisie unifiée + 4 vues dashboard (mensuel, déclarations URSSAF/TVA)

This commit is contained in:
2026-06-30 08:21:14 +02:00
parent 54f9fc56f4
commit 80042faf0f
29 changed files with 808 additions and 437 deletions
+185 -97
View File
@@ -1,96 +1,110 @@
"""
Logique fiscale — JB Compta.
Bases de calcul : CA HORS TAXES (HT) encaissé.
- Base des cotisations URSSAF et du versement libératoire (assujetti TVA).
- En franchise de TVA, HT = TTC : le calcul reste correct.
Multi-activité : une entreprise peut exercer plusieurs activités (ex. Vente BIC
+ Prestation BIC). Taux URSSAF, abattements, taux VL et seuils diffèrent par
activité, donc tout est ventilé par activité puis totalisé.
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.
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 django.db.models import Sum
from django.db.models.functions import TruncMonth
from .models import Facture, Depense, Bareme, StatutFacture, Activite
from .models import (Facture, Depense, LigneFacture, Bareme, StatutFacture,
Activite, RegimeTva)
CENT = Decimal("0.01")
MOIS_FR = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet",
"Août", "Septembre", "Octobre", "Novembre", "Décembre"]
def _q(value):
return (value or Decimal("0")).quantize(CENT)
def _q(v):
return (v or Decimal("0")).quantize(CENT)
def factures_encaissees(entreprise, annee):
return Facture.objects.filter(
entreprise=entreprise,
statut=StatutFacture.ENCAISSEE,
date_encaissement__year=annee,
)
# ---- Sélections de période ---------------------------------------------
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
def depenses_annee(entreprise, annee):
return Depense.objects.filter(entreprise=entreprise, date__year=annee)
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
def ca_ht_par_activite(entreprise, annee):
"""Dict {activite: CA HT encaissé}."""
rows = (
factures_encaissees(entreprise, annee)
.values("activite")
.annotate(total=Sum("montant_ht"))
)
# ---- CA -----------------------------------------------------------------
def ca_ht_par_activite(entreprise, annee, mois=None):
rows = factures_encaissees(entreprise, annee, mois).values("activite").annotate(
total=Sum("montant_ht"))
return {r["activite"]: _q(r["total"]) for r in rows}
def ca_ht(entreprise, annee):
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_ht"))
return _q(agg["s"])
def ca_ht(entreprise, annee, mois=None):
return _q(factures_encaissees(entreprise, annee, mois).aggregate(
s=Sum("montant_ht"))["s"])
# ---- TVA ----------------------------------------------------------------
def tva_sur_ventes(entreprise, annee):
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_tva"))
return _q(agg["s"])
def tva_sur_ventes(entreprise, annee, mois=None):
return _q(factures_encaissees(entreprise, annee, mois).aggregate(
s=Sum("montant_tva"))["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)),
def tva_autoliquidee(entreprise, annee, mois=None):
total = sum((d.tva_autoliquidee for d in depenses_periode(entreprise, annee, mois)),
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)),
def tva_deductible(entreprise, annee, mois=None):
total = sum((d.tva_deductible_value for d in depenses_periode(entreprise, annee, mois)),
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))
def tva_collectee(entreprise, annee, mois=None):
return _q(tva_sur_ventes(entreprise, annee, mois)
+ tva_autoliquidee(entreprise, annee, mois))
# ---- CA mensuel ---------------------------------------------------------
def tva_par_taux(entreprise, annee, mois=None):
"""Ventilation des ventes encaissées par taux de TVA : {taux: (base_ht, tva)}."""
lignes = LigneFacture.objects.filter(
facture__in=factures_encaissees(entreprise, annee, mois))
par_taux = {}
for l in lignes.select_related("facture"):
base = l.montant_ht
taux = l.taux_tva
b, t = par_taux.get(taux, (Decimal("0"), Decimal("0")))
par_taux[taux] = (b + base, t + base * taux / Decimal("100"))
return {taux: (_q(b), _q(t)) for taux, (b, t) in sorted(par_taux.items())}
def intracom_periode(entreprise, annee, mois=None):
"""Acquisitions UE/import auto-liquidées : (base_ht, tva)."""
base = Decimal("0")
tva = Decimal("0")
for d in depenses_periode(entreprise, annee, mois):
if d.autoliquidee and not d.neutre:
base += d.montant_ht
tva += d.tva_autoliquidee
return _q(base), _q(tva)
# ---- CA mensuel (graphe) ------------------------------------------------
def ca_ht_mensuel(entreprise, annee):
"""Liste de 12 valeurs (index 0 = janvier) du CA HT encaissé (toutes activités)."""
rows = (
factures_encaissees(entreprise, annee)
.annotate(mois=TruncMonth("date_encaissement"))
.values("mois")
.annotate(total=Sum("montant_ht"))
)
rows = (factures_encaissees(entreprise, annee)
.annotate(mois=TruncMonth("date_encaissement"))
.values("mois").annotate(total=Sum("montant_ht")))
mensuel = [Decimal("0")] * 12
for r in rows:
if r["mois"]:
@@ -101,7 +115,6 @@ def ca_ht_mensuel(entreprise, annee):
# ---- Détail par activité ------------------------------------------------
def detail_activite(activite, ca, bareme):
"""Calculs fiscaux pour une activité donnée."""
label = dict(Activite.choices).get(activite, activite)
d = {"activite": activite, "label": label, "ca_ht": ca, "bareme": bareme}
if bareme:
@@ -111,72 +124,147 @@ def detail_activite(activite, ca, bareme):
"taux_vl": bareme.taux_vl,
"versement_liberatoire": _q(ca * bareme.taux_vl / Decimal("100")),
"abattement_fiscal": bareme.abattement_fiscal,
"revenu_imposable": _q(
ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")),
"revenu_imposable": _q(ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")),
"seuil_ca": bareme.seuil_ca,
"pct_seuil_ca": (_q(ca / bareme.seuil_ca * Decimal("100"))
if bareme.seuil_ca else Decimal("0")),
"pct_seuil_ca": (_q(ca / bareme.seuil_ca * Decimal("100")) if bareme.seuil_ca else Decimal("0")),
"seuil_tva_base": bareme.seuil_tva_base,
"depasse_seuil_tva": ca > bareme.seuil_tva_base,
"depasse_seuil_ca": ca > bareme.seuil_ca,
})
else:
d.update({
"taux_urssaf": None, "cotisations": None,
"taux_vl": None, "versement_liberatoire": None,
"abattement_fiscal": None, "revenu_imposable": None,
"seuil_ca": None, "pct_seuil_ca": None,
"seuil_tva_base": None, "depasse_seuil_tva": False,
"depasse_seuil_ca": False,
})
for k in ("taux_urssaf", "cotisations", "taux_vl", "versement_liberatoire",
"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
def synthese(entreprise, annee):
"""Tableau de bord chiffré, ventilé par activité puis totalisé."""
ca_par_act = ca_ht_par_activite(entreprise, annee)
activites = set(ca_par_act) | {entreprise.activite}
def _lignes_activite(entreprise, annee, mois=None):
ca_par = ca_ht_par_activite(entreprise, annee, mois)
activites = set(ca_par) | {entreprise.activite}
baremes = {b.activite: b for b in Bareme.objects.filter(annee=annee)}
return [detail_activite(a, ca_par.get(a, Decimal("0")), baremes.get(a))
for a in sorted(activites)]
lignes = []
for act in sorted(activites):
ca = ca_par_act.get(act, Decimal("0"))
lignes.append(detail_activite(act, ca, baremes.get(act)))
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
def _somme(lignes, 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
# ---- Vue globale (annuelle) --------------------------------------------
def synthese(entreprise, annee):
lignes = _lignes_activite(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,
"annee": annee,
"lignes_activite": lignes,
"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("cotisations"),
"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,
"cotisations_urssaf": _somme(lignes, "cotisations"),
"versement_liberatoire": (_somme(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),
"ca_mensuel": ca_ht_mensuel(entreprise, annee),
}
# ---- Vue mensuelle ------------------------------------------------------
def tableau_mensuel(entreprise, annee):
baremes = {b.activite: b for b in Bareme.objects.filter(annee=annee)}
rows = []
for m in range(1, 13):
ca_par = ca_ht_par_activite(entreprise, annee, m)
cot = Decimal("0")
vl = Decimal("0")
for act, ca in ca_par.items():
b = baremes.get(act)
if b:
cot += ca * b.taux_urssaf / Decimal("100")
vl += ca * b.taux_vl / Decimal("100")
tva_col = tva_collectee(entreprise, annee, m)
tva_ded = tva_deductible(entreprise, annee, m)
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,
"tva_collectee": tva_col, "tva_deductible": tva_ded,
"tva_a_reverser": _q(tva_col - tva_ded),
})
return rows
# ---- Déclaration URSSAF (période) --------------------------------------
def declaration_urssaf(entreprise, annee, mois=None):
lignes = _lignes_activite(entreprise, annee, mois)
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")
if entreprise.versement_liberatoire else None),
}
# ---- Déclaration TVA (période) -----------------------------------------
def declaration_tva(entreprise, annee, mois=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)
tva_brute = _q(tva_ventes + intra_tva)
a_payer = _q(tva_brute - tva_ded)
def couple(taux):
return par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00")))
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")))
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),
"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"),
},
}
def annees_disponibles(entreprise):
qs = (
Facture.objects.filter(entreprise=entreprise,
statut=StatutFacture.ENCAISSEE,
date_encaissement__isnull=False)
.dates("date_encaissement", "year")
)
qs = Facture.objects.filter(entreprise=entreprise, statut=StatutFacture.ENCAISSEE,
date_encaissement__isnull=False).dates("date_encaissement", "year")
return sorted({d.year for d in qs}, reverse=True)