""" Import d'un relevé d'opérations au format CSV. Colonnes : date ; libelle ; categorie ; activite ; taux_tva ; regime ; montant - montant négatif = dépense, positif = vente (ou neutre si catégorie neutre). Aperçu avant enregistrement. """ import csv import io from decimal import Decimal, InvalidOperation from datetime import datetime from django.contrib.auth.decorators import login_required from django.shortcuts import render from .models import (Entreprise, Client, Facture, LigneFacture, Paiement, Depense, Categorie, Activite, RegimeTva) COLONNES = ["date", "libelle", "categorie", "activite", "taux_tva", "regime", "montant"] CENT = Decimal("0.01") def _dec(v, d="0"): try: return Decimal(str(v or d).replace(",", ".").replace(" ", "").strip() or d) except (InvalidOperation, AttributeError): return Decimal(d) def _date(s): s = (s or "").strip() for fmt in ("%Y-%m-%d", "%d/%m/%Y", "%d-%m-%Y"): try: return datetime.strptime(s, fmt).date() except ValueError: continue return None def _activite(s): s = (s or "").strip().lower() table = {"vente": "vente", "service_bic": "service_bic", "service_bnc": "service_bnc", "bic": "service_bic", "bnc": "service_bnc"} return table.get(s, "") def _regime(s): s = (s or "").strip().lower() if s in ("intracom", "intracommunautaire", "ue"): return RegimeTva.INTRACOM if s in ("import", "hors ue"): return RegimeTva.IMPORT return RegimeTva.NATIONALE def _parse(text, cats): """Retourne (lignes, erreurs). Chaque ligne = dict prêt à créer + aperçu.""" text = text.lstrip("") delim = ";" if text.count(";") >= text.count(",") else "," reader = csv.reader(io.StringIO(text), delimiter=delim) rows = list(reader) if not rows: return [], ["Fichier vide."] # En-tête optionnel start = 1 if rows and rows[0] and rows[0][0].strip().lower() in ("date", "date ") else 0 lignes, erreurs = [], [] for n, raw in enumerate(rows[start:], start=start + 1): if not any(c.strip() for c in raw): continue c = (raw + [""] * 7)[:7] dt = _date(c[0]) montant = _dec(c[6]) if dt is None: erreurs.append(f"Ligne {n} : date invalide « {c[0]} ».") continue if montant == 0: erreurs.append(f"Ligne {n} : montant nul.") continue cat = cats.get((c[2] or "").strip().lower()) taux = _dec(c[4]) regime = _regime(c[5]) neutre = bool(cat and cat.exclure_calculs) if montant > 0: typ = "Mouvement neutre" if neutre else "Vente" else: typ = "Dépense neutre" if neutre else "Dépense" lignes.append({ "n": n, "date": dt, "libelle": (c[1] or "").strip() or "(sans libellé)", "categorie": cat, "categorie_nom": (c[2] or "").strip(), "activite": _activite(c[3]), "taux": taux, "regime": regime, "montant": montant, "neutre": neutre, "type": typ, }) return lignes, erreurs def _creer(entreprise, l): montant = l["montant"] if montant > 0: if l["neutre"]: Paiement.objects.create(entreprise=entreprise, date=l["date"], libelle=l["libelle"], montant_total=montant, sens="credit", categorie=l["categorie"]) return "vente_neutre" client, _ = Client.objects.get_or_create(entreprise=entreprise, nom=l["libelle"]) ttc = montant ht = (ttc / (Decimal("1") + l["taux"] / Decimal("100"))).quantize(CENT) annee = l["date"].year base = Facture.objects.filter(entreprise=entreprise, numero__startswith=f"{annee}-").count() + 1 numero = f"{annee}-{base:03d}" while Facture.objects.filter(entreprise=entreprise, numero=numero).exists(): base += 1 numero = f"{annee}-{base:03d}" f = Facture.objects.create( entreprise=entreprise, client=client, numero=numero, activite=l["activite"] or entreprise.activite, date_emission=l["date"], date_encaissement=l["date"], statut="encaissee") LigneFacture.objects.create(facture=f, designation=l["libelle"], quantite=Decimal("1"), prix_unitaire_ht=ht, taux_tva=l["taux"]) f.recompute_totaux() return "vente" # Dépense abs_m = -montant if l["regime"] in (RegimeTva.INTRACOM, RegimeTva.IMPORT): ttc, tva = abs_m, Decimal("0") elif l["neutre"]: ttc, tva = abs_m, Decimal("0") else: ttc = abs_m tva = (abs_m * l["taux"] / (Decimal("100") + l["taux"])).quantize(CENT) if l["taux"] else Decimal("0") Depense.objects.create( entreprise=entreprise, date=l["date"], libelle=l["libelle"], categorie=l["categorie"], activite=l["activite"], regime_tva=l["regime"], taux_tva=l["taux"] if l["regime"] != RegimeTva.NATIONALE else Decimal("0"), montant_ttc=ttc, montant_tva=tva) return "depense" @login_required def import_csv(request): entreprises = Entreprise.accessibles(request.user) cats = {c.nom.strip().lower(): c for c in Categorie.objects.all()} ctx = {"entreprises": entreprises, "colonnes": COLONNES} if request.method == "POST": entreprise = entreprises.filter(pk=request.POST.get("entreprise")).first() texte = request.POST.get("texte", "") fichier = request.FILES.get("fichier") if fichier: texte = fichier.read().decode("utf-8-sig", "ignore") lignes, erreurs = _parse(texte, cats) ctx.update({"lignes": lignes, "erreurs": erreurs, "texte": texte, "entreprise_id": int(request.POST.get("entreprise") or 0)}) if request.POST.get("action") == "import": if not entreprise: erreurs.append("Sélectionne une entreprise.") if not lignes: erreurs.append("Aucune ligne valide à importer.") if not erreurs: compteur = {} for l in lignes: k = _creer(entreprise, l) compteur[k] = compteur.get(k, 0) + 1 ctx["resultat"] = compteur ctx["lignes"] = None # import fait ctx["erreurs"] = erreurs return render(request, "compta/import_csv.html", ctx)