Files
jb-compta/indy-mcp/indy_mapping.py
T

105 lines
3.6 KiB
Python

"""
Mapping des transactions Indy -> lignes d'import JB Compta.
Indy ne renvoie pas un nom de catégorie mais un **numéro de compte du plan
comptable (PCG)** dans `subdivisions[].accounting_account.number`. On mappe ce
numéro vers nos catégories. Bien plus fiable que du texte.
Sortie : un dict par transaction au format attendu par l'import JB Compta :
{date, libelle, categorie, activite, taux, regime, montant}
"""
from decimal import Decimal
# Préfixe de compte PCG -> nom de catégorie JB Compta (le préfixe le plus long gagne).
COMPTE_CATEGORIE = [
("701", "Vente de produits finis"),
("707", "Vente de produits finis"),
("706", "Prestation de services"),
("605", "Matériel et outillage"),
("606", "Matériel et outillage"),
("607", "Matériel et outillage"),
("6256", "Frais de repas hors domicile"),
("6253", "Frais de repas hors domicile"),
("626", "Télécom, fournitures, documentation"),
("6181", "Télécom, fournitures, documentation"),
("6183", "Télécom, fournitures, documentation"),
("6136", "Abonnement logiciel"),
("6226", "Abonnement logiciel"),
("646", "Cotisation sociale Urssaf"),
("44551", "TVA payée"),
("44558", "TVA payée"),
("44583", "Remboursement de TVA"),
("108", "Prélèvement personnel"),
# 471 (compte d'attente) et 6252 (déplacements) -> non mappés (à catégoriser)
]
# Compte de vente -> activité (pour le multi-activité)
COMPTE_ACTIVITE = [
("701", "vente"), ("707", "vente"),
("706", "service_bic"),
]
def _cat(num):
num = str(num or "")
best = None
for pref, cat in COMPTE_CATEGORIE:
if num.startswith(pref) and (best is None or len(pref) > len(best[0])):
best = (pref, cat)
return best[1] if best else None
def _act(num):
num = str(num or "")
for pref, act in COMPTE_ACTIVITE:
if num.startswith(pref):
return act
return ""
def normaliser(tx):
"""Transaction Indy -> ligne d'import (ou None si inexploitable)."""
try:
montant = (Decimal(tx["totalAmountInCents"]) / 100).quantize(Decimal("0.01"))
except Exception:
return None
subs = [s for s in tx.get("subdivisions", [])
if not str((s.get("accounting_account") or {}).get("number", "")).startswith("512")]
if not subs:
return None
# Subdivision principale = plus gros montant absolu (hors compte bancaire 512).
main = max(subs, key=lambda s: abs(s.get("amount_in_cents", 0)))
num = str((main.get("accounting_account") or {}).get("number", ""))
intra = main.get("tva_intracom")
if intra and intra.get("type") == "eu":
regime = "intracom"
taux = (Decimal(intra.get("rate", 0)) / 100)
else:
regime = "nationale"
taux = (Decimal(main.get("tva_rate", 0)) / 100)
return {
"date": tx.get("date", ""),
"libelle": tx.get("description") or tx.get("raw_description") or "(sans libellé)",
"categorie": _cat(num),
"activite": _act(num),
"taux": taux,
"regime": regime,
"montant": montant,
"compte": num,
}
def vers_csv(transactions):
"""Liste de transactions Indy -> CSV au format import JB Compta."""
lignes = ["date;libelle;categorie;activite;taux_tva;regime;montant"]
for tx in transactions:
r = normaliser(tx)
if not r:
continue
regime = "" if r["regime"] == "nationale" else r["regime"]
taux = f"{r['taux']:.2f}".rstrip("0").rstrip(".")
lignes.append(";".join([
r["date"], r["libelle"], r["categorie"] or "", r["activite"],
taux, regime, f"{r['montant']:.2f}"]))
return "\n".join(lignes)