Arrondi URSSAF/TVA/VFL a l'euro + CFP + fix montant popup + fix categorie favori
Deploy via Portainer / deploy (push) Successful in 0s

This commit is contained in:
2026-07-15 11:32:19 +02:00
parent 8524f11ec8
commit cd5605819a
9 changed files with 83 additions and 25 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ class CategorieAdmin(admin.ModelAdmin):
@admin.register(Bareme) @admin.register(Bareme)
class BaremeAdmin(admin.ModelAdmin): class BaremeAdmin(admin.ModelAdmin):
list_display = ("annee", "activite", "taux_urssaf", "abattement_fiscal", list_display = ("annee", "activite", "taux_urssaf", "abattement_fiscal",
"taux_vl", "seuil_ca", "seuil_tva_base") "taux_vl", "taux_cfp", "seuil_ca", "seuil_tva_base")
list_filter = ("annee", "activite") list_filter = ("annee", "activite")
+7 -5
View File
@@ -12,13 +12,14 @@ from django.contrib.auth.models import Group, Permission
from compta.models import Bareme, Categorie, Activite from compta.models import Bareme, Categorie, Activite
from compta.categories_indy import CATALOGUE from compta.categories_indy import CATALOGUE
# activite, taux_urssaf, abattement, taux_vl, seuil_ca, seuil_tva_base, seuil_tva_majore, taux_cfp
BAREMES_2026 = [ BAREMES_2026 = [
(Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"), (Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"),
Decimal("203100"), Decimal("85000"), Decimal("93500")), Decimal("203100"), Decimal("85000"), Decimal("93500"), Decimal("0.10")),
(Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"), (Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"),
Decimal("83600"), Decimal("37500"), Decimal("41250")), Decimal("83600"), Decimal("37500"), Decimal("41250"), Decimal("0.30")),
(Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"), (Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"),
Decimal("83600"), Decimal("37500"), Decimal("41250")), Decimal("83600"), Decimal("37500"), Decimal("41250"), Decimal("0.20")),
] ]
GROUPE_NOM = "Gestionnaire entreprise" GROUPE_NOM = "Gestionnaire entreprise"
@@ -46,11 +47,12 @@ def charger_groupes():
def charger_reference(stdout=None, style=None): def charger_reference(stdout=None, style=None):
for act, urssaf, ab, vl, sca, tb, tm in BAREMES_2026: for act, urssaf, ab, vl, sca, tb, tm, cfp in BAREMES_2026:
Bareme.objects.update_or_create( Bareme.objects.update_or_create(
activite=act, annee=2026, activite=act, annee=2026,
defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab, taux_vl=vl, defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab, taux_vl=vl,
seuil_ca=sca, seuil_tva_base=tb, seuil_tva_majore=tm)) seuil_ca=sca, seuil_tva_base=tb, seuil_tva_majore=tm,
taux_cfp=cfp))
cat = {} cat = {}
for i, (nom, usage, excl, compte) in enumerate(CATALOGUE): for i, (nom, usage, excl, compte) in enumerate(CATALOGUE):
c, _ = Categorie.objects.update_or_create( c, _ = Categorie.objects.update_or_create(
+19
View File
@@ -0,0 +1,19 @@
# Generated by Django 5.2.15 on 2026-07-15 09:28
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compta', '0010_categorie_compte_pcg'),
]
operations = [
migrations.AddField(
model_name='bareme',
name='taux_cfp',
field=models.DecimalField(decimal_places=2, default=Decimal('0'), help_text='Contribution à la formation professionnelle : vente 0,1 % · artisan 0,3 % · service/libéral 0,2 %.', max_digits=4, verbose_name='Taux CFP (%)'),
),
]
+4
View File
@@ -62,6 +62,10 @@ class Bareme(models.Model):
taux_urssaf = models.DecimalField("Taux cotisations URSSAF (%)", max_digits=5, decimal_places=2) taux_urssaf = models.DecimalField("Taux cotisations URSSAF (%)", max_digits=5, decimal_places=2)
abattement_fiscal = models.DecimalField("Abattement forfaitaire (%)", max_digits=5, decimal_places=2) abattement_fiscal = models.DecimalField("Abattement forfaitaire (%)", max_digits=5, decimal_places=2)
taux_vl = models.DecimalField("Taux versement libératoire (%)", max_digits=4, decimal_places=2) taux_vl = models.DecimalField("Taux versement libératoire (%)", max_digits=4, decimal_places=2)
taux_cfp = models.DecimalField(
"Taux CFP (%)", max_digits=4, decimal_places=2, default=Decimal("0"),
help_text="Contribution à la formation professionnelle : vente 0,1 % · "
"artisan 0,3 % · service/libéral 0,2 %.")
seuil_ca = models.DecimalField("Plafond CA micro (€)", max_digits=12, decimal_places=2) seuil_ca = models.DecimalField("Plafond CA micro (€)", max_digits=12, decimal_places=2)
seuil_tva_base = models.DecimalField("Seuil franchise TVA (€)", max_digits=12, decimal_places=2) seuil_tva_base = models.DecimalField("Seuil franchise TVA (€)", max_digits=12, decimal_places=2)
seuil_tva_majore = models.DecimalField("Seuil franchise TVA majoré (€)", max_digits=12, decimal_places=2) seuil_tva_majore = models.DecimalField("Seuil franchise TVA majoré (€)", max_digits=12, decimal_places=2)
+39 -16
View File
@@ -5,7 +5,7 @@ Bases : CA HORS TAXES (HT) encaissé.
TVA collectée = TVA ventes + TVA auto-liquidée (UE/import). TVA collectée = TVA ventes + TVA auto-liquidée (UE/import).
TVA déductible = TVA achats nationaux + TVA auto-liquidée. Neutres exclus. TVA déductible = TVA achats nationaux + TVA auto-liquidée. Neutres exclus.
""" """
from decimal import Decimal from decimal import Decimal, ROUND_HALF_UP
from django.db.models import Sum from django.db.models import Sum
from django.db.models.functions import TruncMonth from django.db.models.functions import TruncMonth
@@ -21,6 +21,16 @@ def _q(v):
return (v or Decimal("0")).quantize(CENT) return (v or Decimal("0")).quantize(CENT)
def _euro(v):
"""Arrondi à l'euro le plus proche (règle URSSAF / TVA : 0,50 → euro sup.)."""
return (v or Decimal("0")).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
def _somme_euro(lignes, cle):
vals = [l[cle] for l in lignes if l.get(cle) is not None]
return _euro(sum(vals, Decimal("0"))) if vals else None
# ---- Sélections de période --------------------------------------------- # ---- Sélections de période ---------------------------------------------
def factures_encaissees(entreprise, annee, mois=None): def factures_encaissees(entreprise, annee, mois=None):
@@ -120,9 +130,11 @@ def detail_activite(activite, ca, bareme):
if bareme: if bareme:
d.update({ d.update({
"taux_urssaf": bareme.taux_urssaf, "taux_urssaf": bareme.taux_urssaf,
"cotisations": _q(ca * bareme.taux_urssaf / Decimal("100")), "cotisations": _euro(ca * bareme.taux_urssaf / Decimal("100")),
"taux_vl": bareme.taux_vl, "taux_vl": bareme.taux_vl,
"versement_liberatoire": _q(ca * bareme.taux_vl / Decimal("100")), "versement_liberatoire": _euro(ca * bareme.taux_vl / Decimal("100")),
"taux_cfp": bareme.taux_cfp,
"cfp": _euro(ca * bareme.taux_cfp / Decimal("100")),
"abattement_fiscal": bareme.abattement_fiscal, "abattement_fiscal": bareme.abattement_fiscal,
"revenu_imposable": _q(ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")), "revenu_imposable": _q(ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")),
"seuil_ca": bareme.seuil_ca, "seuil_ca": bareme.seuil_ca,
@@ -133,8 +145,8 @@ def detail_activite(activite, ca, bareme):
}) })
else: else:
for k in ("taux_urssaf", "cotisations", "taux_vl", "versement_liberatoire", for k in ("taux_urssaf", "cotisations", "taux_vl", "versement_liberatoire",
"abattement_fiscal", "revenu_imposable", "seuil_ca", "pct_seuil_ca", "taux_cfp", "cfp", "abattement_fiscal", "revenu_imposable",
"seuil_tva_base"): "seuil_ca", "pct_seuil_ca", "seuil_tva_base"):
d[k] = None d[k] = None
d["depasse_seuil_tva"] = d["depasse_seuil_ca"] = False d["depasse_seuil_tva"] = d["depasse_seuil_ca"] = False
return d return d
@@ -165,13 +177,13 @@ def synthese(entreprise, annee):
"entreprise": entreprise, "annee": annee, "lignes_activite": lignes, "entreprise": entreprise, "annee": annee, "lignes_activite": lignes,
"bareme_manquant": any(l["bareme"] is None and l["ca_ht"] for l in lignes), "bareme_manquant": any(l["bareme"] is None and l["ca_ht"] for l in lignes),
"ca_ht": ca_ht(entreprise, annee), "ca_ht": ca_ht(entreprise, annee),
"cotisations_urssaf": _somme(lignes, "cotisations"), "cotisations_urssaf": _somme_euro(lignes, "cotisations"),
"versement_liberatoire": (_somme(lignes, "versement_liberatoire") "versement_liberatoire": (_somme_euro(lignes, "versement_liberatoire")
if entreprise.versement_liberatoire else None), if entreprise.versement_liberatoire else None),
"revenu_imposable": _somme(lignes, "revenu_imposable"), "revenu_imposable": _somme(lignes, "revenu_imposable"),
"tva_sur_ventes": tva_ventes, "tva_autoliquidee": tva_autoliq, "tva_sur_ventes": tva_ventes, "tva_autoliquidee": tva_autoliq,
"tva_collectee": tva_col, "tva_deductible": tva_ded, "tva_collectee": tva_col, "tva_deductible": tva_ded,
"tva_a_reverser": _q(tva_col - tva_ded), "tva_a_reverser": _euro(_euro(tva_col) - _euro(tva_ded)),
"ca_mensuel": ca_ht_mensuel(entreprise, annee), "ca_mensuel": ca_ht_mensuel(entreprise, annee),
} }
@@ -195,10 +207,10 @@ def tableau_mensuel(entreprise, annee):
rows.append({ rows.append({
"mois": m, "nom": MOIS_FR[m - 1], "mois": m, "nom": MOIS_FR[m - 1],
"ca_ht": ca_ht(entreprise, annee, m), "ca_ht": ca_ht(entreprise, annee, m),
"cotisations": _q(cot), "cotisations": _euro(cot),
"versement_liberatoire": _q(vl) if entreprise.versement_liberatoire else None, "versement_liberatoire": _euro(vl) if entreprise.versement_liberatoire else None,
"tva_collectee": tva_col, "tva_deductible": tva_ded, "tva_collectee": tva_col, "tva_deductible": tva_ded,
"tva_a_reverser": _q(tva_col - tva_ded), "tva_a_reverser": _euro(_euro(tva_col) - _euro(tva_ded)),
}) })
return rows return rows
@@ -207,14 +219,23 @@ def tableau_mensuel(entreprise, annee):
def declaration_urssaf(entreprise, annee, mois=None): def declaration_urssaf(entreprise, annee, mois=None):
lignes = _lignes_activite(entreprise, annee, mois) lignes = _lignes_activite(entreprise, annee, mois)
# URSSAF : le CA déclaré et les cotisations sont arrondis à l'euro le plus proche.
for l in lignes:
l["ca_ht"] = _euro(l["ca_ht"])
return { return {
"entreprise": entreprise, "annee": annee, "mois": mois, "entreprise": entreprise, "annee": annee, "mois": mois,
"mois_nom": MOIS_FR[mois - 1] if mois else "Année entière", "mois_nom": MOIS_FR[mois - 1] if mois else "Année entière",
"lignes": lignes, "lignes": lignes,
"ca_ht": ca_ht(entreprise, annee, mois), "ca_ht": _euro(ca_ht(entreprise, annee, mois)),
"cotisations": _somme(lignes, "cotisations"), "cotisations": _somme_euro(lignes, "cotisations"),
"versement_liberatoire": (_somme(lignes, "versement_liberatoire") "cfp": _somme_euro(lignes, "cfp"),
"versement_liberatoire": (_somme_euro(lignes, "versement_liberatoire")
if entreprise.versement_liberatoire else None), if entreprise.versement_liberatoire else None),
"total_regle": _euro(
(_somme_euro(lignes, "cotisations") or Decimal("0"))
+ (_somme_euro(lignes, "cfp") or Decimal("0"))
+ ((_somme_euro(lignes, "versement_liberatoire") or Decimal("0"))
if entreprise.versement_liberatoire else Decimal("0"))),
} }
@@ -225,8 +246,10 @@ def declaration_tva(entreprise, annee, mois=None):
intra_base, intra_tva = intracom_periode(entreprise, annee, mois) intra_base, intra_tva = intracom_periode(entreprise, annee, mois)
tva_ventes = sum((t for _, t in par_taux.values()), Decimal("0")) tva_ventes = sum((t for _, t in par_taux.values()), Decimal("0"))
tva_ded = tva_deductible(entreprise, annee, mois) tva_ded = tva_deductible(entreprise, annee, mois)
tva_brute = _q(tva_ventes + intra_tva) # CA3 : TVA collectée, déductible et à payer arrondies à l'euro le plus proche.
a_payer = _q(tva_brute - tva_ded) tva_brute = _euro(tva_ventes + intra_tva)
tva_ded = _euro(tva_ded)
a_payer = tva_brute - tva_ded
def couple(taux): def couple(taux):
return par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00"))) return par_taux.get(Decimal(taux), (Decimal("0.00"), Decimal("0.00")))
+1 -1
View File
@@ -51,7 +51,7 @@
{% verbatim %} {% verbatim %}
<script> <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 ss=(tx.subdivisions||[]).filter(s=>{const nn=String((s.accounting_account||{}).number||'');return !nn.startsWith('512')&&!s.is_tva;});if(!ss.length)return [];const md=tx.tva_selected||(tx.vat&&tx.vat.tvaSelected)||'tva_ht';const inc=md==='tva_ttc';const gid=tx._id||'';const lib=(tx.description||tx.raw_description||'').replace(/;/g,' ');return ss.map(s=>{const num=String((s.accounting_account||{}).number||'');const amt=s.amount_in_cents||0,tv=s.tva_amount_in_cents||0;const ttc=((inc?amt:amt+tv)/100).toFixed(2);let rg='',t2=0;if(s.tva_intracom&&s.tva_intracom.type==='eu'){rg='intracom';t2=(s.tva_intracom.rate||0)/100;}else{t2=(s.tva_rate||0)/100;}return[tx.date||'',lib,cat(num),act(num),String(t2),rg,ttc,gid].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=[].concat(...all.map(norm));const NL=String.fromCharCode(10);const csv='date;libelle;categorie;activite;taux_tva;regime;montant;groupe'+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 ss=(tx.subdivisions||[]).filter(s=>{const nn=String((s.accounting_account||{}).number||'');return !nn.startsWith('512')&&!s.is_tva;});if(!ss.length)return [];const md=tx.tva_selected||(tx.vat&&tx.vat.tvaSelected)||'tva_ht';const inc=md==='tva_ttc';const gid=tx._id||'';const lib=(tx.description||tx.raw_description||'').replace(/;/g,' ');return ss.map(s=>{const num=String((s.accounting_account||{}).number||'');const amt=s.amount_in_cents||0,tv=s.tva_amount_in_cents||0;const ttc=((inc?amt:amt+tv)/100).toFixed(2);let rg='',t2=0;if(s.tva_intracom&&s.tva_intracom.type==='eu'){rg='intracom';t2=(s.tva_intracom.rate||0)/100;}else{t2=(s.tva_rate||0)/100;}return[tx.date||'',lib,num,act(num),String(t2),rg,ttc,gid].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=[].concat(...all.map(norm));const NL=String.fromCharCode(10);const csv='date;libelle;categorie;activite;taux_tva;regime;montant;groupe'+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('lnk').href = BM;
document.getElementById('ta').value = BM; document.getElementById('ta').value = BM;
</script> </script>
+7
View File
@@ -18,6 +18,7 @@
<table> <table>
<thead> <thead>
<tr><th>Activité</th><th>CA HT à déclarer</th><th>Taux URSSAF</th><th>Cotisations</th> <tr><th>Activité</th><th>CA HT à déclarer</th><th>Taux URSSAF</th><th>Cotisations</th>
<th>CFP</th>
{% if decl.versement_liberatoire is not None %}<th>Taux VL</th><th>Impôt (VL)</th>{% endif %}</tr> {% if decl.versement_liberatoire is not None %}<th>Taux VL</th><th>Impôt (VL)</th>{% endif %}</tr>
</thead> </thead>
<tbody> <tbody>
@@ -27,6 +28,7 @@
<td>{{ l.ca_ht }} €</td> <td>{{ l.ca_ht }} €</td>
<td>{% if l.taux_urssaf is not None %}{{ l.taux_urssaf }} %{% else %}—{% endif %}</td> <td>{% if l.taux_urssaf is not None %}{{ l.taux_urssaf }} %{% else %}—{% endif %}</td>
<td>{% if l.cotisations is not None %}{{ l.cotisations }} €{% else %}—{% endif %}</td> <td>{% if l.cotisations is not None %}{{ l.cotisations }} €{% else %}—{% endif %}</td>
<td>{% if l.cfp is not None %}{{ l.cfp }} €{% else %}—{% endif %}</td>
{% if decl.versement_liberatoire is not None %} {% if decl.versement_liberatoire is not None %}
<td>{% if l.taux_vl is not None %}{{ l.taux_vl }} %{% else %}—{% endif %}</td> <td>{% if l.taux_vl is not None %}{{ l.taux_vl }} %{% else %}—{% endif %}</td>
<td>{% if l.versement_liberatoire is not None %}{{ l.versement_liberatoire }} €{% else %}—{% endif %}</td> <td>{% if l.versement_liberatoire is not None %}{{ l.versement_liberatoire }} €{% else %}—{% endif %}</td>
@@ -35,8 +37,13 @@
{% endfor %} {% endfor %}
<tr class="total"> <tr class="total">
<td>Total</td><td>{{ decl.ca_ht }} €</td><td></td><td>{{ decl.cotisations|default:"—" }} €</td> <td>Total</td><td>{{ decl.ca_ht }} €</td><td></td><td>{{ decl.cotisations|default:"—" }} €</td>
<td>{{ decl.cfp|default:"—" }} €</td>
{% if decl.versement_liberatoire is not None %}<td></td><td>{{ decl.versement_liberatoire }} €</td>{% endif %} {% if decl.versement_liberatoire is not None %}<td></td><td>{{ decl.versement_liberatoire }} €</td>{% endif %}
</tr> </tr>
<tr class="total">
<td colspan="3">Total cotisations et contributions à régler</td>
<td colspan="{% if decl.versement_liberatoire is not None %}4{% else %}2{% endif %}">{{ decl.total_regle|default:"—" }} €</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
+1 -1
View File
@@ -115,7 +115,7 @@
data-type="{{ r.type }}" data-id="{{ r.id }}" data-type="{{ r.type }}" data-id="{{ r.id }}"
data-libelle="{{ r.libelle }}" data-libelle="{{ r.libelle }}"
data-date="{{ r.date|date:'Y-m-d' }}" data-date="{{ r.date|date:'Y-m-d' }}"
data-montant="{{ r.montant|floatformat:2|unlocalize }}" data-montant="{{ r.montant|unlocalize }}"
data-cat="{{ r.categorie_id }}" data-cat="{{ r.categorie_id }}"
data-activite="{{ r.activite }}" data-activite="{{ r.activite }}"
data-regime="{{ r.regime }}" data-regime="{{ r.regime }}"
+4 -1
View File
@@ -99,6 +99,8 @@ def _parse_csv(text, cats):
delim = ";" if text.count(";") >= text.count(",") else "," delim = ";" if text.count(";") >= text.count(",") else ","
rows = list(csv.reader(io.StringIO(text), delimiter=delim)) rows = list(csv.reader(io.StringIO(text), delimiter=delim))
lignes, erreurs = [], [] lignes, erreurs = [], []
# Résolution par numéro de compte PCG (favori Indy) OU par nom (CSV manuel).
compte_map = {c.compte_pcg: c for c in Categorie.objects.all() if c.compte_pcg}
start = 1 if rows and rows[0] and rows[0][0].strip().lower() == "date" else 0 start = 1 if rows and rows[0] and rows[0][0].strip().lower() == "date" else 0
for n, raw in enumerate(rows[start:], start=start + 1): for n, raw in enumerate(rows[start:], start=start + 1):
if not any(c.strip() for c in raw): if not any(c.strip() for c in raw):
@@ -110,7 +112,8 @@ def _parse_csv(text, cats):
erreurs.append(f"Ligne {n} : date invalide « {c[0]} »."); continue erreurs.append(f"Ligne {n} : date invalide « {c[0]} »."); continue
if montant == 0: if montant == 0:
erreurs.append(f"Ligne {n} : montant nul."); continue erreurs.append(f"Ligne {n} : montant nul."); continue
cat = cats.get((c[2] or "").strip().lower()) val = (c[2] or "").strip()
cat = compte_map.get(val) or cats.get(val.lower())
grp = (c[7] or "").strip() # colonne « groupe » (ventilation), optionnelle grp = (c[7] or "").strip() # colonne « groupe » (ventilation), optionnelle
lignes.append(_row(dt, (c[1] or "").strip() or "(sans libellé)", montant, lignes.append(_row(dt, (c[1] or "").strip() or "(sans libellé)", montant,
_dec(c[4]), _regime(c[5]), cat, _activite(c[3]), _dec(c[4]), _regime(c[5]), cat, _activite(c[3]),