Python

Python & finance PME — un kit opérationnel en 7 scripts (simple, fiable, actionnable)

×

Recommandés

CSV en Python — du téléchargement au...
Le CSV paraît simple, jusqu’au...
En savoir plus
Python : pandas.to_csv — Exporter propre, fiable...
DataFrame.to_csv devient un contrat d’échange :...
En savoir plus
Python : pandas.read_csv — Guide pratique enrichi
Pourquoi read_csv reste incontournable Parce qu’il...
En savoir plus
Pratique de l’apprentissage automatique avec scikit-learn et...
Cet article vous guide, pas à...
En savoir plus
Python pour la finance — les fonctions...
Python est devenu l’outil “couteau suisse”...
En savoir plus
Python & Pickle : manipuler les fichiers...
Pickle est la bibliothèque standard de...
En savoir plus

Pour une PME, la finance est un pilotage quotidien — TVA à déclarer, relances clients, trésorerie à 13 semaines, marges par produit, suivi budget vs réalisé. Voici un kit minimaliste en Python (pandas) pour automatiser 80 % de ces tâches sans usine à gaz. Tout est pensé “PME française”, fichiers Excel/CSV, et résultats exportables en un clic.

Prérequis (une fois)

python -m pip install --upgrade pip
pip install pandas numpy openpyxl

1) Arborescence claire et fichiers d’entrée

/fin_pme/
  data/
    ventes.xlsx            # factures de vente
    achats.xlsx            # factures d'achat
    banque.csv             # relevé bancaire
    stock.csv              # valorisation stock (optionnel)
    budget_mensuel.csv     # budget par mois et compte
  out/
    ...

Colonnes suggérées (ventes/achats)

  • Date, Echeance, Statut (Emise, Payée, Partielle)
  • Client / Fournisseur
  • Ref, Famille, Quantite, PU_HT, Montant_HT, Taux_TVA, Montant_TVA, Montant_TTC
  • Devise (optionnel), TxChange (optionnel)

2) Import & normalisation (1 script)

import pandas as pd
from pathlib import Path

BASE = Path("fin_pme")
D = BASE/"data"

def _parse_date(s): return pd.to_datetime(s, dayfirst=True, errors="coerce")

def load_data():
    ventes  = pd.read_excel(D/"ventes.xlsx")
    achats  = pd.read_excel(D/"achats.xlsx")
    banque  = pd.read_csv(D/"banque.csv", sep=";", encoding="utf-8")
    for df, cols in [(ventes, ["Date","Echeance"]), (achats, ["Date","Echeance"])]:
        for c in cols: df[c] = _parse_date(df[c])
        for c in ["Montant_HT","Montant_TVA","Montant_TTC","Taux_TVA","Quantite","PU_HT"]:
            if c in df.columns: df[c] = pd.to_numeric(df[c], errors="coerce")
    banque["Date"] = _parse_date(banque["Date"])
    return ventes, achats, banque

ventes, achats, banque = load_data()

3) TVA collectée / déductible par taux (+ garde-fous pro-rata)

def tva_par_taux(df, sens="collectee"):
    # sens: 'collectee' pour ventes, 'deductible' pour achats
    grp = df.groupby("Taux_TVA", as_index=False)["Montant_TVA"].sum()
    grp = grp.rename(columns={"Montant_TVA": f"TVA_{sens}"})
    total = grp[f"TVA_{sens}"].sum()
    return grp, total

tva_v, total_collectee = tva_par_taux(ventes, "collectee")
tva_a, total_deduct   = tva_par_taux(achats, "deductible")

tva = pd.merge(tva_v, tva_a, on="Taux_TVA", how="outer").fillna(0.0)
tva["Solde_par_taux"] = tva["TVA_collectee"] - tva["TVA_deductible"]
solde_global = total_collectee - total_deduct

# Garde-fous (exemples) :
assert (ventes["Montant_TVA"]>=0).all(), "TVA ventes négative détectée"
assert (achats["Montant_TVA"]>=0).all(), "TVA achats négative détectée"

Astuce : si vous êtes en pro-rata, ajoutez une colonne ProRata (0–1) aux factures concernées et appliquez TVA_deductible_effective = Montant_TVA * ProRata avant l’agrégation.


4) Balance âgée & DSO/DPO (clients/fournisseurs)

import numpy as np
aujourdhui = pd.Timestamp.today().normalize()

def open_amount(df, sens="client"):
    # Montant ouvert = TTC non payé (simplifié)
    # Suppose une colonne 'Payé' (TTC encaissé/décaissé), sinon adaptez.
    if "Paye" in df.columns:
        return df["Montant_TTC"].fillna(0) - df["Paye"].fillna(0)
    # à défaut, on considère les statuts
    m = df["Statut"].str.lower().fillna("")
    return np.where(m.eq("payée")|m.eq("payee")|m.eq("payé"), 0.0, df["Montant_TTC"].fillna(0))

def aging_buckets(d):
    delta = (aujourdhui - d).days
    if pd.isna(delta): return "NA"
    if delta <= 0:      return "Non échu"
    if delta <= 30:     return "1–30 j"
    if delta <= 60:     return "31–60 j"
    if delta <= 90:     return "61–90 j"
    return ">90 j"

def balance_agee(df, tiers_col):
    df = df.copy()
    df["Ouvert"] = open_amount(df)
    df["Tranche"] = df["Echeance"].apply(aging_buckets)
    piv = df.pivot_table(index=tiers_col, columns="Tranche", values="Ouvert", aggfunc="sum", fill_value=0.0)
    piv["Total"] = piv.sum(axis=1)
    return piv.sort_values("Total", ascending=False)

ba_clients = balance_agee(ventes, "Client")
ba_fourn   = balance_agee(achats, "Fournisseur")

def dso(ventes):
    ca_jour = ventes.groupby(ventes["Date"].dt.to_period("M"))["Montant_TTC"].sum().mean()/30
    encours = open_amount(ventes).sum()
    return encours / ca_jour if ca_jour>0 else np.nan

def dpo(achats):
    achats_jour = achats.groupby(achats["Date"].dt.to_period("M"))["Montant_TTC"].sum().mean()/30
    dettes = open_amount(achats).sum()
    return dettes / achats_jour if achats_jour>0 else np.nan

DSO = round(dso(ventes), 1)
DPO = round(dpo(achats), 1)

Lecture : DSO (Days Sales Outstanding) = jours de crédit client, DPO = jours de crédit fournisseur. Avec un stock moyen en jours, vous calculez le Cash Conversion Cycle = DSO + Jours de stock − DPO.


5) Trésorerie 13 semaines (simple et robuste)

def echeancier(df, tiers_col):
    df = df.copy()
    df["Ouvert"] = open_amount(df)
    df = df[df["Ouvert"]>0]
    df["Semaine"] = df["Echeance"].dt.to_period("W").apply(lambda p: p.start_time.date())
    return df.groupby(["Semaine", tiers_col], as_index=False)["Ouvert"].sum()

ech_clients = echeancier(ventes, "Client")      # encaissements attendus
ech_fourn   = echeancier(achats, "Fournisseur") # décaissements attendus

cal = pd.DataFrame({"Semaine": pd.date_range(aujourdhui, periods=13, freq="W-MON").date})
entrees = ech_clients.groupby("Semaine")["Ouvert"].sum().reindex(cal["Semaine"], fill_value=0.0).values
sorties = ech_fourn.groupby("Semaine")["Ouvert"].sum().reindex(cal["Semaine"], fill_value=0.0).values
cal["Entrées_prevues"] = entrees
cal["Sorties_prevues"] = sorties
cal["Net"] = cal["Entrées_prevues"] - cal["Sorties_prevues"]

# Ajoutez solde d'ouverture banque si vous l'avez :
solde_ouverture = 25_000.0
cal["Trésorerie_proj"] = solde_ouverture + cal["Net"].cumsum()

Astuce : ajoutez un coefficient de fiabilité par client (1 = toujours à l’heure, 0,7 = retards) et multipliez Ouvert par ce coefficient pour une prévision conservatrice.


6) Marges par produit & “ABC light” (sans ERP)

# Marge brute par facture/ligne
if {"Quantite","PU_HT","Montant_HT"}.issubset(ventes.columns):
    ventes["CA_HT"] = ventes["Montant_HT"]
else:
    ventes["CA_HT"] = ventes["Quantite"]*ventes["PU_HT"]

# Coûts variables unitaires par 'Famille' (fichier simple ou dictionnaire)
couts_var = {
    "Café": 0.35, "Snack": 1.10, "Service": 0.0
}
ventes["CV_unit"] = ventes["Famille"].map(couts_var).fillna(0.0)
ventes["CV_total"] = ventes["CV_unit"]*ventes["Quantite"].fillna(1.0)
ventes["Marge_brute"] = ventes["CA_HT"] - ventes["CV_total"]

marge_par_famille = ventes.groupby("Famille", as_index=False)["CA_HT","Marge_brute"].sum()
marge_par_famille["Taux_marge"] = marge_par_famille["Marge_brute"]/marge_par_famille["CA_HT"]

ABC light : si vous avez 2–3 inducteurs (heures machine, km, palettes), ventilez un coût fixe proportionnellement à ces inducteurs pour approcher un coût complet sans ERP.


7) Rapprochement bancaire (niveau 1, exact match)

# banque: Date, Libelle, Montant (positif = entrée, négatif = sortie)
# ventes payées: constituez un 'encaissements.csv' avec Date, Ref, Montant
# achats payés:  'decaissements.csv'

enc = pd.DataFrame({"Date":ventes["Date"], "Montant":ventes["Montant_TTC"]})
dec = pd.DataFrame({"Date":achats["Date"], "Montant":-achats["Montant_TTC"]})
mouvements = pd.concat([enc, dec], ignore_index=True).dropna(subset=["Montant"])

# Matching exact par montant et journée
bk = banque.copy()
bk["Date"] = bk["Date"].dt.date
mouvements["Date"] = mouvements["Date"].dt.date

matched = pd.merge(bk, mouvements, on=["Date","Montant"], how="inner")
bk_restes = pd.merge(bk, matched[["Date","Montant"]], on=["Date","Montant"], how="left", indicator=True)
bk_restes = bk_restes[bk_restes["_merge"].eq("left_only")].drop(columns=["_merge"])

# 'bk_restes' = lignes non rapprochées (écarts, agrégats, frais)

Niveau 2 : faites un matching par fenêtre de date (±3 jours) et montants proches (frais) ; attention aux faux positifs → conservez un journal des appariements.


8) Budget vs Réel & alertes (seuils)

# budget_mensuel.csv: Mois (YYYY-MM), Compte, Budget
budget = pd.read_csv(D/"budget_mensuel.csv")
budget["Mois"] = pd.PeriodIndex(budget["Mois"], freq="M")

ventes["Mois"] = ventes["Date"].dt.to_period("M")
achats["Mois"] = achats["Date"].dt.to_period("M")

ca_mensuel = ventes.groupby("Mois")["Montant_TTC"].sum().rename("CA_reel")
ach_mensuel = achats.groupby("Mois")["Montant_TTC"].sum().rename("Achats_reels")
real = pd.concat([ca_mensuel, ach_mensuel], axis=1).fillna(0)

bv = budget.pivot_table(index="Mois", columns="Compte", values="Budget", aggfunc="sum").fillna(0)
# Suppose deux comptes: 'CA' et 'Achats'
bv = bv.rename(columns={"CA":"CA_budget","Achats":"Achats_budget"})

report = pd.concat([real, bv], axis=1)
report["Ecart_CA"] = report["CA_reel"] - report["CA_budget"]
report["Ecart_Achats"] = report["Achats_reels"] - report["Achats_budget"]

# Alertes simples
ALERTE = []
if report["Ecart_CA"].iloc[-1] < -0.05 * report["CA_budget"].iloc[-1]:
    ALERTE.append("CA du mois en retard >5% vs budget.")
if report["Ecart_Achats"].iloc[-1] > 0.05 * report["Achats_budget"].iloc[-1]:
    ALERTE.append("Achats du mois dépassent le budget >5%.")

9) Exports Excel prêts à partager

OUT = BASE/"out"; OUT.mkdir(parents=True, exist_ok=True)

with pd.ExcelWriter(OUT/"PME_Rapports_Financiers.xlsx", engine="openpyxl") as xw:
    tva.to_excel(xw, sheet_name="TVA", index=False)
    ba_clients.to_excel(xw, sheet_name="Balance_agee_clients")
    ba_fourn.to_excel(xw, sheet_name="Balance_agee_fourn")
    cal.to_excel(xw, sheet_name="Tresorerie_13_sem", index=False)
    marge_par_famille.to_excel(xw, sheet_name="Marge_par_famille", index=False)
    report.reset_index().to_excel(xw, sheet_name="Budget_vs_Reel", index=False)

print("Export OK ->", OUT/"PME_Rapports_Financiers.xlsx")

10) Lecture managériale (ce que ça change demain matin)

  • TVA en 2 minutes : vous consolidez par taux, contrôlez les anomalies, préparez la déclaration.
  • Relances ciblées : la balance âgée vous donne les 10 clients à appeler, tranche par tranche.
  • Cash clair : la trésorerie 13 semaines met en évidence la semaine qui bascule au rouge si un gros client paye en retard.
  • Marge produit : une vision par famille révèle où vous gagnez réellement de l’argent.
  • Discipline budgétaire : un report mensualisé et deux alertes suffisent à remettre la trajectoire dans le bon axe.
  • Banque : un rapprochement niveau 1 élimine 70 % du manuel, le reste devient un travail de qualité (écarts, frais, agrégats).

Check-list “mise en prod PME”

  • Colonnes standardisées (dates, montants, statuts).
  • Contrôles de cohérence (TVA ≥ 0, dates d’échéance renseignées).
  • Sauvegarde data/ et export unique out/PME_Rapports_Financiers.xlsx.
  • Procédure hebdo : import → TVA → balance âgée → trésorerie 13 sem → export (30–45 min).
  • Historiser out/ par semaine/mois pour la traçabilité.

In fine,

Ce kit Python remplace des manipulations manuelles par des scripts courts et lisibles. Vous gagnez du temps sur la TVA, les relances, la trésorerie et les marges, avec des exports Excel prêts à envoyer.

Recommandés

CSV en Python — du téléchargement au...
Le CSV paraît simple, jusqu’au...
En savoir plus
Python : pandas.to_csv — Exporter propre, fiable...
DataFrame.to_csv devient un contrat d’échange :...
En savoir plus
Python : pandas.read_csv — Guide pratique enrichi
Pourquoi read_csv reste incontournable Parce qu’il...
En savoir plus
Pratique de l’apprentissage automatique avec scikit-learn et...
Cet article vous guide, pas à...
En savoir plus
Python pour la finance — les fonctions...
Python est devenu l’outil “couteau suisse”...
En savoir plus
Python & Pickle : manipuler les fichiers...
Pickle est la bibliothèque standard de...
En savoir plus
AZ

Recent Posts

Outils interactifs : Cartographie des processus et grille d’audit interne ISO 9001

Deux outils concrets pour piloter la qualité sans alourdir vos équipes Un système qualité n’avance…

6 heures ago

Exemple de fiche de préparation de chantier

Un chantier se gagne souvent avant même l’arrivée des équipes. Quand tout est clair dès…

1 jour ago

Texte argumentatif sur le mariage forcé

Le mariage a du sens quand il repose sur une décision libre, mûrie et partagée.…

1 jour ago

Étude de cas en droit : Modèle Word à suivre

Une étude de cas réussie commence par une structure sûre. Ce modèle Word vous guide…

4 jours ago

Soft skills : la liste A à Z interactive pour trier, comparer et choisir vos compétences clés

Les soft skills se repèrent vite sur une fiche, mais elles ne pèsent vraiment que…

4 jours ago

Comparateur de verres progressifs

Outil de comparaison et repérage des offres étudiantes Choisir des verres progressifs ressemble rarement à…

5 jours ago

This website uses cookies.