Files
jb-compta/compta/services.py
T

155 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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). Les taux URSSAF, abattements, taux de versement libératoire 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).
"""
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
CENT = Decimal("0.01")
def _q(value):
return (value or Decimal("0")).quantize(CENT)
def factures_encaissees(entreprise, annee):
return Facture.objects.filter(
entreprise=entreprise,
statut=StatutFacture.ENCAISSEE,
date_encaissement__year=annee,
)
def ca_ht_par_activite(entreprise, annee):
"""Dict {activite: CA HT encaissé}."""
rows = (
factures_encaissees(entreprise, annee)
.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 tva_collectee(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 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"))
)
mensuel = [Decimal("0")] * 12
for r in rows:
if r["mois"]:
mensuel[r["mois"].month - 1] = _q(r["total"])
return mensuel
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:
d.update({
"taux_urssaf": bareme.taux_urssaf,
"cotisations": _q(ca * bareme.taux_urssaf / Decimal("100")),
"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")),
"seuil_ca": bareme.seuil_ca,
"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,
})
return d
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)}
lignes = []
for act in sorted(activites):
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_ded = tva_deductible(entreprise, annee)
return {
"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_collectee": tva_col,
"tva_deductible": tva_ded,
"tva_a_reverser": _q(tva_col - tva_ded),
"ca_mensuel": ca_ht_mensuel(entreprise, annee),
}
def annees_disponibles(entreprise):
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)