Favori Indy servi par Django + selecteur de date FR

This commit is contained in:
2026-06-30 22:59:13 +02:00
parent 53fbf19a0b
commit e5138c60bd
7 changed files with 117 additions and 37 deletions
Binary file not shown.
Binary file not shown.
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JB Compta — Favori Indy → CSV</title>
<style>
body{font-family:system-ui,Segoe UI,Roboto,sans-serif;background:#0f172a;color:#e2e8f0;
max-width:760px;margin:0 auto;padding:28px;line-height:1.5;}
h1{font-size:22px} h2{font-size:16px;color:#94a3b8;margin-top:26px}
a{color:#38bdf8}
a.bm{display:inline-block;background:#38bdf8;color:#04263a;font-weight:700;
padding:10px 18px;border-radius:10px;text-decoration:none;font-size:16px}
ol{padding-left:20px} li{margin:6px 0}
textarea{width:100%;height:120px;background:#1e293b;color:#e2e8f0;border:1px solid #334155;
border-radius:8px;padding:10px;font-family:ui-monospace,monospace;font-size:12px}
code{background:#1e293b;padding:1px 6px;border-radius:5px}
.note{color:#94a3b8;font-size:14px}
.warn{background:#7f1d1d;padding:10px 14px;border-radius:8px;margin-top:10px}
</style>
</head>
<body>
<div class="note"><a href="/import/">← Retour à l'import</a></div>
<h1>Favori « Indy → CSV »</h1>
<p class="note">Un clic sur la page Indy → tes transactions sont copiées au format CSV,
prêtes à coller dans JB Compta. Aucun token à chercher, la 2FA est déjà passée, et tout
se fait dans ton navigateur (donc indépendant de l'accès réseau du serveur).</p>
<h2>Installation (30 s)</h2>
<ol>
<li>Affiche ta <b>barre de favoris</b> (Ctrl+Maj+B dans Chrome).</li>
<li><b>Glisse-dépose</b> ce bouton dans la barre de favoris :
&nbsp; <a class="bm" id="lnk" href="#">Indy → CSV</a></li>
<li>Va sur <code>app.indy.fr</code> (connecté), puis <b>clique le favori</b>.</li>
<li>Choisis la période (sélecteur de date, format JJ/MM/AAAA) → le CSV est copié →
colle-le dans <a href="/import/">la page d'import</a>.</li>
</ol>
<div class="warn">Si le glisser-déposer ne marche pas : crée un favori manuellement,
puis colle le code ci-dessous comme <b>URL</b> du favori.</div>
<h2>Code (installation manuelle)</h2>
<textarea id="ta" readonly onclick="this.select()"></textarea>
<h2>Notes</h2>
<ul class="note">
<li>À utiliser <b>uniquement</b> quand l'onglet actif est <code>app.indy.fr</code>.</li>
<li>Aucun secret n'est stocké : le favori lit ta session en cours.</li>
<li>Si une alerte « Erreur Indy » apparaît, reconnecte-toi à Indy et réessaie.</li>
</ul>
{% verbatim %}
<script>
var BM = "javascript:(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='',t2=0;if(m.tva_intracom&&m.tva_intracom.type==='eu'){rg='intracom';t2=(m.tva_intracom.rate||0)/100;}else{t2=(m.tva_rate||0)/100;}const lib=(tx.description||tx.raw_description||'').replace(/;/g,' ');return[tx.date||'',lib,cat(num),act(num),String(t2),rg,mt].join(';');};const mk=(t,css)=>{const e=document.createElement(t);if(css)e.style.cssText=css;return e;};const askDates=()=>new Promise(res=>{const t=new Date(),y=t.getFullYear(),mo=String(t.getMonth()+1).padStart(2,'0'),ld=String(new Date(y,t.getMonth()+1,0).getDate()).padStart(2,'0');const ov=mk('div','position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483647;display:flex;align-items:center;justify-content:center');const bx=mk('div','background:#fff;color:#111;padding:20px;border-radius:12px;font-family:sans-serif;min-width:280px');const h=mk('div','font-weight:700;margin-bottom:12px');h.textContent='Indy vers CSV : choisis la periode';const l1=mk('div','font-size:12px;color:#555');l1.textContent='Du';const i1=mk('input','width:100%;margin:4px 0 10px;padding:6px');i1.type='date';i1.value=y+'-'+mo+'-01';const l2=mk('div','font-size:12px;color:#555');l2.textContent='Au';const i2=mk('input','width:100%;margin:4px 0 14px;padding:6px');i2.type='date';i2.value=y+'-'+mo+'-'+ld;const row=mk('div','text-align:right');const an=mk('button','margin-right:8px;padding:6px 12px');an.textContent='Annuler';const ok=mk('button','background:#38bdf8;border:none;padding:6px 14px;border-radius:6px;cursor:pointer');ok.textContent='Recuperer';row.appendChild(an);row.appendChild(ok);[h,l1,i1,l2,i2,row].forEach(x=>bx.appendChild(x));ov.appendChild(bx);document.body.appendChild(ov);ok.onclick=()=>{const a=i1.value,b=i2.value;document.body.removeChild(ov);res([a,b]);};an.onclick=()=>{document.body.removeChild(ov);res([null,null]);};});const[d1,d2]=await askDates();if(!d1||!d2)return;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 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);return;}const rows=all.map(norm).filter(Boolean);const NL=String.fromCharCode(10);const csv='date;libelle;categorie;activite;taux_tva;regime;montant'+NL+rows.join(NL);try{await navigator.clipboard.writeText(csv);alert(rows.length+' operations -> CSV copie ! Colle-le dans JB Compta (/import/).');}catch(e){window.prompt('Copie ce CSV (Ctrl+C) :',csv);}})();";
document.getElementById('lnk').href = BM;
document.getElementById('ta').value = BM;
</script>
{% endverbatim %}
</body>
</html>
+2 -1
View File
@@ -59,7 +59,8 @@
<form method="post" class="card indy">
{% csrf_token %}
<h2>① Synchroniser depuis Indy (recommandé)</h2>
<div class="sub">Récupère tes transactions directement depuis l'API Indy. Anti-doublon automatique.</div>
<div class="sub">Récupère tes transactions directement depuis l'API Indy. Anti-doublon automatique.
· <a href="/import/bookmarklet/">📌 Installer le favori « Indy → CSV »</a> (le plus simple, depuis ton navigateur)</div>
<div class="grid3">
<div>
<label>Entreprise</label>
+2
View File
@@ -2,6 +2,7 @@ from django.urls import path
from . import views
from . import views_saisie
from . import views_import
from . import views_bookmarklet
urlpatterns = [
path("dashboard/", views.dashboard, name="dashboard"),
@@ -10,6 +11,7 @@ urlpatterns = [
path("declaration/tva/", views.declaration_tva, name="declaration_tva"),
path("saisie/", views_saisie.saisie_paiement, name="saisie_paiement"),
path("import/", views_import.import_csv, name="import_csv"),
path("import/bookmarklet/", views_bookmarklet.bookmarklet, name="bookmarklet"),
path("export/ventes/", views.export_ventes, name="export_ventes"),
path("export/tva/", views.export_tva, name="export_tva"),
path("export/urssaf/", views.export_urssaf, name="export_urssaf"),
+9 -22
View File
@@ -20,35 +20,22 @@
</head>
<body>
<h1>Favori « Indy → CSV »</h1>
<p class="note">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).</p>
<p class="note">Identique à la page servie par l'app (/import/bookmarklet/). Un clic sur Indy →
CSV copié, prêt à coller dans JB Compta. Pas de token, 2FA déjà passée, tout dans le navigateur.</p>
<h2>Installation (30 s)</h2>
<h2>Installation</h2>
<ol>
<li>Affiche ta <b>barre de favoris</b> (Ctrl+Maj+B dans Chrome).</li>
<li><b>Glisse-dépose</b> ce bouton dans la barre de favoris :
&nbsp; <a class="bm" id="lnk" href="#">Indy → CSV</a></li>
<li>Va sur <code>app.indy.fr</code> (connecté), puis <b>clique le favori</b>.</li>
<li>Saisis la période → le CSV est copié → colle-le dans <code>/import/</code> de JB Compta.</li>
<li>Affiche la barre de favoris (Ctrl+Maj+B).</li>
<li><b>Glisse</b> ce bouton dans la barre : &nbsp; <a class="bm" id="lnk" href="#">Indy → CSV</a></li>
<li>Sur <code>app.indy.fr</code> (connecté), clique le favori, choisis la période (JJ/MM/AAAA).</li>
<li>Colle le CSV dans <code>/import/</code>.</li>
</ol>
<div class="warn">Si le glisser-déposer ne marche pas : crée un favori manuellement,
puis colle le code ci-dessous comme <b>URL</b> du favori.</div>
<h2>Code (installation manuelle)</h2>
<div class="warn">Si le glisser-déposer échoue, crée un favori manuel et colle ce code comme URL :</div>
<textarea id="ta" readonly onclick="this.select()"></textarea>
<h2>Notes</h2>
<ul class="note">
<li>À utiliser <b>uniquement</b> quand l'onglet actif est <code>app.indy.fr</code>.</li>
<li>Aucun secret n'est stocké : le favori lit ta session en cours.</li>
<li>Si une alerte « Erreur Indy » apparaît, reconnecte-toi à Indy et réessaie.</li>
<li>Le mapping des catégories (compte comptable → catégorie) est le même que dans l'app.</li>
</ul>
<script>
var BM = "javascript:(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='',t2=0;if(m.tva_intracom&&m.tva_intracom.type==='eu'){rg='intracom';t2=(m.tva_intracom.rate||0)/100;}else{t2=(m.tva_rate||0)/100;}const lib=(tx.description||tx.raw_description||'').replace(/;/g,' ');return[tx.date||'',lib,cat(num),act(num),String(t2),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 debut (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);return;}const rows=all.map(norm).filter(Boolean);const NL=String.fromCharCode(10);const csv='date;libelle;categorie;activite;taux_tva;regime;montant'+NL+rows.join(NL);try{await navigator.clipboard.writeText(csv);alert(rows.length+' operations -> CSV copie ! Colle-le dans JB Compta (/import/).');}catch(e){window.prompt('Copie ce CSV (Ctrl+C) :',csv);}})();";
var BM = "javascript:(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='',t2=0;if(m.tva_intracom&&m.tva_intracom.type==='eu'){rg='intracom';t2=(m.tva_intracom.rate||0)/100;}else{t2=(m.tva_rate||0)/100;}const lib=(tx.description||tx.raw_description||'').replace(/;/g,' ');return[tx.date||'',lib,cat(num),act(num),String(t2),rg,mt].join(';');};const mk=(t,css)=>{const e=document.createElement(t);if(css)e.style.cssText=css;return e;};const askDates=()=>new Promise(res=>{const t=new Date(),y=t.getFullYear(),mo=String(t.getMonth()+1).padStart(2,'0'),ld=String(new Date(y,t.getMonth()+1,0).getDate()).padStart(2,'0');const ov=mk('div','position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483647;display:flex;align-items:center;justify-content:center');const bx=mk('div','background:#fff;color:#111;padding:20px;border-radius:12px;font-family:sans-serif;min-width:280px');const h=mk('div','font-weight:700;margin-bottom:12px');h.textContent='Indy vers CSV : choisis la periode';const l1=mk('div','font-size:12px;color:#555');l1.textContent='Du';const i1=mk('input','width:100%;margin:4px 0 10px;padding:6px');i1.type='date';i1.value=y+'-'+mo+'-01';const l2=mk('div','font-size:12px;color:#555');l2.textContent='Au';const i2=mk('input','width:100%;margin:4px 0 14px;padding:6px');i2.type='date';i2.value=y+'-'+mo+'-'+ld;const row=mk('div','text-align:right');const an=mk('button','margin-right:8px;padding:6px 12px');an.textContent='Annuler';const ok=mk('button','background:#38bdf8;border:none;padding:6px 14px;border-radius:6px;cursor:pointer');ok.textContent='Recuperer';row.appendChild(an);row.appendChild(ok);[h,l1,i1,l2,i2,row].forEach(x=>bx.appendChild(x));ov.appendChild(bx);document.body.appendChild(ov);ok.onclick=()=>{const a=i1.value,b=i2.value;document.body.removeChild(ov);res([a,b]);};an.onclick=()=>{document.body.removeChild(ov);res([null,null]);};});const[d1,d2]=await askDates();if(!d1||!d2)return;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 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);return;}const rows=all.map(norm).filter(Boolean);const NL=String.fromCharCode(10);const csv='date;libelle;categorie;activite;taux_tva;regime;montant'+NL+rows.join(NL);try{await navigator.clipboard.writeText(csv);alert(rows.length+' operations -> CSV copie ! Colle-le dans JB Compta (/import/).');}catch(e){window.prompt('Copie ce CSV (Ctrl+C) :',csv);}})();";
document.getElementById('lnk').href = BM;
document.getElementById('ta').value = BM;
</script>
+44 -14
View File
@@ -1,13 +1,15 @@
/*
* Bookmarklet « Indy → CSV » — version lisible (documentation).
* Le bouton prêt à installer est dans bookmarklet.html.
* Le bouton prêt à installer est dans bookmarklet.html, et la même page est servie
* par l'application à /import/bookmarklet/.
*
* 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/.
* Usage : être connecté sur app.indy.fr, cliquer le favori. Une petite fenêtre
* s'ouvre avec deux SÉLECTEURS DE DATE (format JJ/MM/AAAA). Il récupère ensuite les
* transactions (dans TON navigateur = autorisé, 2FA déjà passée, pas de souci serveur
* ni Cloudflare), construit le CSV au format JB Compta et le copie dans le presse-papier.
*/
(async () => {
// --- mapping compte PCG -> catégorie (identique à compta/indy.py) ---
const CC = [
["701", "Vente de produits finis"], ["707", "Vente de produits finis"],
["706", "Prestation de services"],
@@ -30,12 +32,38 @@
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; }
let rg = "", t2 = 0;
if (m.tva_intracom && m.tva_intracom.type === "eu") { rg = "intracom"; t2 = (m.tva_intracom.rate || 0) / 100; }
else { t2 = (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(";");
return [tx.date || "", lib, cat(num), act(num), String(t2), rg, mt].join(";");
};
// --- fenêtre avec deux sélecteurs de date (input type=date -> JJ/MM/AAAA) ---
const mk = (t, css) => { const e = document.createElement(t); if (css) e.style.cssText = css; return e; };
const askDates = () => new Promise(res => {
const t = new Date(), y = t.getFullYear(),
mo = String(t.getMonth() + 1).padStart(2, "0"),
ld = String(new Date(y, t.getMonth() + 1, 0).getDate()).padStart(2, "0");
const ov = mk("div", "position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483647;display:flex;align-items:center;justify-content:center");
const bx = mk("div", "background:#fff;color:#111;padding:20px;border-radius:12px;font-family:sans-serif;min-width:280px");
const h = mk("div", "font-weight:700;margin-bottom:12px"); h.textContent = "Indy vers CSV : choisis la periode";
const l1 = mk("div", "font-size:12px;color:#555"); l1.textContent = "Du";
const i1 = mk("input", "width:100%;margin:4px 0 10px;padding:6px"); i1.type = "date"; i1.value = y + "-" + mo + "-01";
const l2 = mk("div", "font-size:12px;color:#555"); l2.textContent = "Au";
const i2 = mk("input", "width:100%;margin:4px 0 14px;padding:6px"); i2.type = "date"; i2.value = y + "-" + mo + "-" + ld;
const row = mk("div", "text-align:right");
const an = mk("button", "margin-right:8px;padding:6px 12px"); an.textContent = "Annuler";
const ok = mk("button", "background:#38bdf8;border:none;padding:6px 14px;border-radius:6px;cursor:pointer"); ok.textContent = "Recuperer";
row.appendChild(an); row.appendChild(ok);
[h, l1, i1, l2, i2, row].forEach(x => bx.appendChild(x));
ov.appendChild(bx); document.body.appendChild(ov);
ok.onclick = () => { const a = i1.value, b = i2.value; document.body.removeChild(ov); res([a, b]); };
an.onclick = () => { document.body.removeChild(ov); res([null, null]); };
});
const [d1, d2] = await askDates(); if (!d1 || !d2) return;
// --- token Firebase (depuis IndexedDB) ---
const getTok = () => new Promise(r => {
const o = indexedDB.open("firebaseLocalStorageDb");
o.onsuccess = () => { try {
@@ -45,11 +73,11 @@
} 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;
// --- fetch paginé ---
let all = [], pg = 1, tot = 1e9;
try {
while (all.length < tot) {
@@ -61,9 +89,11 @@
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; }
} catch (e) { alert("Erreur Indy : " + e.message); 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/)."); }
const NL = String.fromCharCode(10);
const csv = "date;libelle;categorie;activite;taux_tva;regime;montant" + NL + rows.join(NL);
try { await navigator.clipboard.writeText(csv); alert(rows.length + " operations -> CSV copie ! Colle-le dans JB Compta (/import/)."); }
catch (e) { window.prompt("Copie ce CSV (Ctrl+C) :", csv); }
})();