diff --git a/compta/__pycache__/indy.cpython-310.pyc b/compta/__pycache__/indy.cpython-310.pyc new file mode 100644 index 0000000..d403d9c Binary files /dev/null and b/compta/__pycache__/indy.cpython-310.pyc differ diff --git a/compta/__pycache__/views_import.cpython-310.pyc b/compta/__pycache__/views_import.cpython-310.pyc new file mode 100644 index 0000000..c93eb67 Binary files /dev/null and b/compta/__pycache__/views_import.cpython-310.pyc differ diff --git a/compta/indy.py b/compta/indy.py index 8f7da5c..44498f7 100644 --- a/compta/indy.py +++ b/compta/indy.py @@ -5,7 +5,7 @@ normalise au format attendu par l'import (réutilisé par la page /import/). La « catégorie » Indy est un numéro de compte du plan comptable (PCG) ; on le mappe vers nos catégories. Montants en centimes, signés. `tva_intracom` => UE. -⚠️ API non officielle d'Indy : pour les données de l'utilisateur. Le token JWT +API non officielle d'Indy : pour les données de l'utilisateur. Le token JWT expire (~1 h) et doit être renouvelé. """ from decimal import Decimal @@ -96,14 +96,26 @@ def fetch_transactions(token, date_debut=None, date_fin=None, search="", page=1, params["dateFrom"] = date_debut if date_fin: params["dateTo"] = date_fin + timeout = httpx.Timeout(12.0, connect=8.0) try: with httpx.Client(base_url=base_url.rstrip("/"), headers=headers, - timeout=30, follow_redirects=True) as c: + timeout=timeout, follow_redirects=True) as c: r = c.get(TRANSACTIONS_PATH, params=params) if r.status_code != 200: - return r.status_code, [], f"HTTP {r.status_code} (token expiré ?)" + return r.status_code, [], ( + f"HTTP {r.status_code} — token expire, ou requete bloquee par " + f"Cloudflare depuis le serveur.") + if "json" not in r.headers.get("content-type", "").lower(): + return r.status_code, [], ( + "Reponse non-JSON (probablement Cloudflare). Le conteneur n'arrive " + "pas a joindre Indy directement. Recupere le CSV depuis ton poste " + "(MCP/CLI) et colle-le ci-dessous.") data = r.json() return 200, data.get("transactions", []), None + except httpx.TimeoutException: + return None, [], ("Delai depasse en joignant Indy : le conteneur n'a peut-etre " + "pas d'acces Internet, ou Cloudflare bloque. Utilise le CSV " + "depuis ton poste.") except Exception as ex: # noqa: BLE001 return None, [], f"{type(ex).__name__}: {ex}" diff --git a/compta/templates/compta/import_csv.html b/compta/templates/compta/import_csv.html index 33b7ea3..8bf24fe 100644 --- a/compta/templates/compta/import_csv.html +++ b/compta/templates/compta/import_csv.html @@ -1,4 +1,4 @@ - +{% load l10n %} @@ -128,8 +128,8 @@ {% for val,lab in regimes %}{% endfor %} - - + + {{ l.type }} diff --git a/compta/views_bookmarklet.py b/compta/views_bookmarklet.py new file mode 100644 index 0000000..14f969f --- /dev/null +++ b/compta/views_bookmarklet.py @@ -0,0 +1,8 @@ +"""Page d'installation du favori « Indy → CSV ».""" +from django.contrib.auth.decorators import login_required +from django.shortcuts import render + + +@login_required +def bookmarklet(request): + return render(request, "compta/bookmarklet.html", {}) diff --git a/entrypoint.sh b/entrypoint.sh index dbc19e5..6ad933e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -20,4 +20,4 @@ if [ "$SEED_DEMO" = "1" ]; then fi echo "→ Démarrage de Gunicorn sur :8000" -exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 +exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 60 diff --git a/indy-mcp/bookmarklet.html b/indy-mcp/bookmarklet.html new file mode 100644 index 0000000..f2ebd13 --- /dev/null +++ b/indy-mcp/bookmarklet.html @@ -0,0 +1,56 @@ + + + + + +Indy → CSV — Favori + + + +

Favori « Indy → CSV »

+

Un clic sur la page Indy → tes transactions sont copiées au format CSV, + prêtes à coller dans JB Compta. Pas de token à chercher, la 2FA est déjà passée, + et tout se fait dans ton navigateur (donc pas de souci d'accès serveur).

+ +

Installation (30 s)

+
    +
  1. Affiche ta barre de favoris (Ctrl+Maj+B dans Chrome).
  2. +
  3. Glisse-dépose ce bouton dans la barre de favoris : +   Indy → CSV
  4. +
  5. Va sur app.indy.fr (connecté), puis clique le favori.
  6. +
  7. Saisis la période → le CSV est copié → colle-le dans /import/ de JB Compta.
  8. +
+ +
Si le glisser-déposer ne marche pas : crée un favori manuellement, + puis colle le code ci-dessous comme URL du favori.
+ +

Code (installation manuelle)

+ + +

Notes

+ + + + + diff --git a/indy-mcp/bookmarklet.js b/indy-mcp/bookmarklet.js new file mode 100644 index 0000000..b229020 --- /dev/null +++ b/indy-mcp/bookmarklet.js @@ -0,0 +1,69 @@ +/* + * Bookmarklet « Indy → CSV » — version lisible (documentation). + * Le bouton prêt à installer est dans bookmarklet.html. + * + * Usage : être connecté sur app.indy.fr, cliquer le favori. Il demande une + * période, récupère les transactions (dans TON navigateur = autorisé, 2FA déjà + * passée, pas de problème Cloudflare ni d'accès serveur), construit le CSV au + * format JB Compta et le copie dans le presse-papier. Tu le colles dans /import/. + */ +(async () => { + const CC = [ + ["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"], + ]; + const CA = [["701", "vente"], ["707", "vente"], ["706", "service_bic"]]; + const cat = (n) => { n = String(n || ""); let b = null; for (const [p, c] of CC) { if (n.startsWith(p) && (!b || p.length > b[0].length)) b = [p, c]; } return b ? b[1] : ""; }; + const act = (n) => { n = String(n || ""); for (const [p, a] of CA) { if (n.startsWith(p)) return a; } return ""; }; + const norm = (tx) => { + const c = tx.totalAmountInCents; if (c == null) return null; + const mt = (c / 100).toFixed(2); + const ss = (tx.subdivisions || []).filter(s => !String((s.accounting_account || {}).number || "").startsWith("512")); + if (!ss.length) return null; + let m = ss[0]; for (const s of ss) if (Math.abs(s.amount_in_cents || 0) > Math.abs(m.amount_in_cents || 0)) m = s; + const num = String((m.accounting_account || {}).number || ""); + let rg = "", tx2 = 0; + if (m.tva_intracom && m.tva_intracom.type === "eu") { rg = "intracom"; tx2 = (m.tva_intracom.rate || 0) / 100; } + else { tx2 = (m.tva_rate || 0) / 100; } + const lib = (tx.description || tx.raw_description || "").replace(/;/g, " "); + return [tx.date || "", lib, cat(num), act(num), String(tx2), rg, mt].join(";"); + }; + const getTok = () => new Promise(r => { + const o = indexedDB.open("firebaseLocalStorageDb"); + o.onsuccess = () => { try { + const q = o.result.transaction("firebaseLocalStorage", "readonly").objectStore("firebaseLocalStorage").getAll(); + q.onsuccess = () => { const v = (q.result || []).map(x => x.value).find(v => v && v.stsTokenManager && v.stsTokenManager.accessToken); r(v ? v.stsTokenManager.accessToken : null); }; + q.onerror = () => r(null); + } catch (e) { r(null); } }; + o.onerror = () => r(null); + }); + const d1 = prompt("Date début (AAAA-MM-JJ)"); if (!d1) return; + const d2 = prompt("Date fin (AAAA-MM-JJ)"); if (!d2) return; + const tk = await getTok(); + const H = { "Accept": "application/json", "x-client-app-type": "web" }; + if (tk) H["Authorization"] = "Bearer " + tk; + let all = [], pg = 1, tot = 1e9; + try { + while (all.length < tot) { + const u = "/api/transactions/transactions-list?" + new URLSearchParams({ search: "", dateFrom: d1, dateTo: d2, page: pg }); + const r = await fetch(u, { headers: H, credentials: "include" }); + if (!r.ok) throw new Error("HTTP " + r.status); + const j = await r.json(); + tot = j.nbTransactions != null ? j.nbTransactions : (j.transactions || []).length; + const t = j.transactions || []; if (!t.length) break; + all = all.concat(t); pg++; if (pg > 60) break; + } + } catch (e) { alert("Erreur Indy : " + e.message + "\nEs-tu bien connecté sur app.indy.fr ?"); return; } + const rows = all.map(norm).filter(Boolean); + const csv = "date;libelle;categorie;activite;taux_tva;regime;montant\n" + rows.join("\n"); + try { await navigator.clipboard.writeText(csv); alert(rows.length + " opérations -> CSV copié ! Colle-le dans JB Compta (/import/)."); } + catch (e) { window.prompt("Copie ce CSV (Ctrl+C) :", csv); } +})();