183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
"""
|
|
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.
|
|
"""
|
|
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 depenses_annee(entreprise, annee):
|
|
return Depense.objects.filter(entreprise=entreprise, date__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"])
|
|
|
|
|
|
# ---- TVA ----------------------------------------------------------------
|
|
|
|
def tva_sur_ventes(entreprise, annee):
|
|
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_tva"))
|
|
return _q(agg["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)),
|
|
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)),
|
|
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))
|
|
|
|
|
|
# ---- CA mensuel ---------------------------------------------------------
|
|
|
|
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
|
|
|
|
|
|
# ---- 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:
|
|
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)
|
|
|
|
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)))
|
|
|
|
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_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,
|
|
"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,
|
|
"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)
|