Gestion des depenses ventilees (multi-taux TVA) a l'import Indy
Deploy via Portainer / deploy (push) Successful in 0s

This commit is contained in:
2026-07-15 10:26:58 +02:00
parent 507ffa283f
commit 2ba1c39bfd
5 changed files with 153 additions and 68 deletions
+64 -12
View File
@@ -78,16 +78,18 @@ def _match_cat(text, cats):
def _row(dt, libelle, montant, taux=Decimal("0"), regime=RegimeTva.NATIONALE,
categorie=None, activite="", indy_id="", n=0):
categorie=None, activite="", indy_id="", n=0, groupe="", n_parts=1):
neutre = bool(categorie and categorie.exclure_calculs)
if montant > 0:
typ = "Mouvement neutre" if neutre else "Vente"
else:
typ = "Dépense neutre" if neutre else "Dépense"
if n_parts > 1:
typ += " (ventilée)"
return {"n": n, "date": dt.isoformat() if dt else "", "libelle": libelle,
"categorie_id": categorie.pk if categorie else "",
"taux": taux, "regime": regime, "montant": montant, "type": typ,
"activite": activite, "indy_id": indy_id}
"activite": activite, "indy_id": indy_id, "groupe": groupe}
# ---- CSV ----------------------------------------------------------------
@@ -248,7 +250,7 @@ def _parse_indy(entreprise, token, date_debut, date_fin, cats_par_nom):
cat = cats_par_nom.get((ln["categorie_nom"] or "").strip().lower())
lignes.append(_row(dt, ln["libelle"], ln["montant"], ln["taux"],
_regime(ln["regime"]), cat, ln["activite"] or "",
ln["indy_id"], n))
ln["indy_id"], n, ln.get("groupe", ""), ln.get("n_parts", 1)))
return lignes, []
@@ -262,6 +264,17 @@ def _deja_importe(entreprise, indy_id):
or Paiement.objects.filter(entreprise=entreprise, indy_id=indy_id).exists())
def _deja_importe_groupe(entreprise, groupe):
"""Vrai si la transaction Indy (ou une de ses parts ventilées) est déjà là."""
if not groupe:
return False
from django.db.models import Q
q = Q(indy_id=groupe) | Q(indy_id__startswith=f"{groupe}#")
return (Facture.objects.filter(entreprise=entreprise).filter(q).exists()
or Depense.objects.filter(entreprise=entreprise).filter(q).exists()
or Paiement.objects.filter(entreprise=entreprise).filter(q).exists())
# ---- Création -----------------------------------------------------------
def _creer(entreprise, dt, libelle, montant, taux, regime, categorie,
@@ -295,6 +308,29 @@ def _creer(entreprise, dt, libelle, montant, taux, regime, categorie,
return "depense"
def _creer_groupe(entreprise, rows):
"""rows = lignes d'une même transaction. Si ≥2 dépenses -> une opération
bancaire (le total) qui porte les dépenses ventilées. Sinon création simple."""
depenses = [r for r in rows if r["montant"] < 0]
if len(rows) >= 2 and len(depenses) == len(rows):
total = sum(-r["montant"] for r in rows)
p = Paiement.objects.create(
entreprise=entreprise, date=rows[0]["dt"], libelle=rows[0]["libelle"],
montant_total=total, sens="debit", indy_id=rows[0].get("groupe", ""))
for r in rows:
Depense.objects.create(
entreprise=entreprise, paiement=p, date=r["dt"], libelle=r["libelle"],
categorie=r["categorie"], activite=r["activite"], regime_tva=r["regime"],
taux_tva=r["taux"], montant_ttc=-r["montant"], indy_id=r["indy_id"])
return {"dépense (ventilée)": len(rows)}
compteur = {}
for r in rows:
k = _creer(entreprise, r["dt"], r["libelle"], r["montant"], r["taux"],
r["regime"], r["categorie"], r["activite"], r["indy_id"])
compteur[k] = compteur.get(k, 0) + 1
return compteur
@login_required
def import_csv(request):
entreprises = Entreprise.accessibles(request.user)
@@ -323,24 +359,40 @@ def import_csv(request):
montants = request.POST.getlist("l_montant")
activites = request.POST.getlist("l_activite")
indy_ids = request.POST.getlist("l_indy_id")
groupes = request.POST.getlist("l_groupe")
cats_by_id = {str(c.pk): c for c in Categorie.objects.all()}
compteur = {}
ignores = 0
# 1) Construire les lignes valides, en préservant l'ordre des groupes.
groupes_ordre = []
par_groupe = {}
for i in range(len(libelles)):
dt = _date(dates[i]) if i < len(dates) else None
montant = _dec(montants[i] if i < len(montants) else "0")
iid = indy_ids[i] if i < len(indy_ids) else ""
if dt is None or montant == 0:
continue
if iid and _deja_importe(entreprise, iid):
ignores += 1
iid = indy_ids[i] if i < len(indy_ids) else ""
grp = (groupes[i] if i < len(groupes) else "") or ""
cle = grp or f"_solo_{i}" # lignes sans groupe = isolées
if cle not in par_groupe:
par_groupe[cle] = []
groupes_ordre.append((cle, grp))
par_groupe[cle].append({
"dt": dt, "montant": montant,
"libelle": (libelles[i] or "").strip() or "(sans libellé)",
"taux": _dec(taux[i] if i < len(taux) else "0"),
"regime": _regime(regimes[i] if i < len(regimes) else ""),
"categorie": cats_by_id.get(cat_ids[i] if i < len(cat_ids) else ""),
"activite": _activite(activites[i] if i < len(activites) else ""),
"indy_id": iid, "groupe": grp})
# 2) Créer groupe par groupe (anti-doublon par transaction).
for cle, grp in groupes_ordre:
rows = par_groupe[cle]
if grp and _deja_importe_groupe(entreprise, grp):
ignores += len(rows)
continue
k = _creer(entreprise, dt, (libelles[i] or "").strip() or "(sans libellé)",
montant, _dec(taux[i] if i < len(taux) else "0"),
_regime(regimes[i] if i < len(regimes) else ""),
cats_by_id.get(cat_ids[i] if i < len(cat_ids) else ""),
_activite(activites[i] if i < len(activites) else ""), iid)
compteur[k] = compteur.get(k, 0) + 1
for k, v in _creer_groupe(entreprise, rows).items():
compteur[k] = compteur.get(k, 0) + v
if ignores:
compteur["déjà importées (ignorées)"] = ignores
ctx["resultat"] = compteur