Version de développement
Cette documentation décrit la version « next » (develop), non encore publiée. Pour la version stable, basculez sur « v5 » dans l'en-tête.
Développeurs

Hooks de rapprochement bancaire

Étendre la page Rapprochement bancaire depuis une application : types de documents, règles de score, rapprochement automatique, validations, événements, actions, colonnes et panneaux JavaScript.

Hooks de rapprochement bancaire

Ces hooks sont en cours d'implémentation (dokos/dokos#504). Cette page décrit le contrat prévu. Elle sera publiée avec la version qui les contient.

Une application peut étendre la page Rapprochement bancaire sans modifier Dokos : ajouter un type de document à rapprocher, changer le classement des documents proposés, rapprocher automatiquement ses propres opérations, réagir à un rapprochement, ajouter des actions, des colonnes et des panneaux.

Tous les hooks se déclarent dans le fichier hooks.py de l'application. Dokos les lit avec frappe.get_hooks() et charge chaque chemin avec frappe.get_attr().

HookValeurRôle
bank_reconciliation_providersDictionnaire {doctype: chemin de classe}Ajoute un type de document à rapprocher
bank_reconciliation_match_rulesListe de chemins de fonctionsModifie le score des documents proposés
auto_reconciliation_methodsListe de chemins de fonctionsRapproche une transaction pendant le rapprochement automatique
bank_reconciliation_batch_reconcilersListe de chemins de fonctionsRapproche un lot de transactions (virements groupés des prestataires de paiement)
bank_reconciliation_validatorsListe de chemins de fonctionsRefuse un rapprochement avant son enregistrement
bank_reconciliation_eventsDictionnaire {on_allocate: [...], on_deallocate: [...]}Réagit à l'ajout ou au retrait d'une ligne de rapprochement
bank_reconciliation_actionsListe de chemins de fonctionsAjoute une action sous le bouton Rapprocher
bank_reconciliation_transaction_columnsListe de chemins de fonctionsAjoute des colonnes à la liste des transactions
bank_reconciliation_jsListe de bundles JavaScriptCharge le JavaScript de l'application dans la page (panneaux, badges)

Périmètre

  • Les hooks d'interface (types de documents en onglets, actions, colonnes, JavaScript) s'appliquent uniquement à la page Rapprochement bancaire de Dokos (/desk/bank-reconciliation).
  • Le panneau latéral de la liste des Transactions bancaires ne reçoit pas ces extensions. Il continue de fonctionner tel quel.
  • L'outil de rapprochement bancaire hérité d'ERPNext et l'application /banking ne sont pas concernés. Ils gardent le hook get_matching_queries. Pour ajouter un type de pièce à leur dialogue « Créer une pièce », voir Types de pièces extensibles.
  • Les fournisseurs (mise à jour du montant non rapproché, date de compensation) et les événements on_allocate / on_deallocate sont appelés par la Transaction bancaire elle-même. Ils s'appliquent donc quel que soit l'écran qui a fait le rapprochement.

bank_reconciliation_providers : ajouter un type de document

Un fournisseur est une classe qui décrit un type de document pour le rapprochement : où trouver les documents candidats, quel tiers, quel compte, quel montant reste à rapprocher. Les types natifs (Écriture de paiement, Écriture de journal, Facture de vente, Facture d'achat) utilisent le même mécanisme.

# mon_app/hooks.py
bank_reconciliation_providers = {
    "Loan Repayment": "mon_app.rapprochement.LoanRepaymentProvider",
}

Si deux applications déclarent un fournisseur pour le même type, la dernière application installée l'emporte. Un type listé dans l'ancien hook bank_reconciliation_doctypes sans fournisseur reçoit un fournisseur générique, qui lit les champs posting_date, unreconciled_amount et clearance_date.

Attributs et méthodes

La classe hérite de erpnext.accounts.bank_reconciliation.provider.BankReconciliationProvider. La classe de base fournit un comportement par défaut pour la plupart des méthodes.

ÉlémentTypeRôle
doctypestrType de document
labelstrLibellé de l'onglet (par défaut, le nom traduit du type)
party_type, party_field, party_name_fieldstrType de tiers et champs du tiers
reference_field, reference_date_fieldstrChamp affiché comme référence, champ de date
directionstuple("in",), ("out",) ou les deux : entrées ou sorties d'argent proposées
creates_payment_entryboolTrue : le rapprochement crée une Écriture de paiement (cas des factures)
sort_orderintPosition de l'onglet (les types natifs utilisent 10 à 40)
columnslist[dict]Colonnes supplémentaires du tableau des documents
get_candidates(filters)-> list[dict]Documents candidats (obligatoire)
get_candidates_by_name(names)-> list[dict]Documents candidats par nom (historique, rapprochement automatique)
get_party_account(doc)-> strCompte de tiers, si creates_payment_entry
get_outstanding_amount(doc)-> floatMontant restant dû, si creates_payment_entry
update_unreconciled_amount(name, bank_account_head, delta)-> NoneMet à jour le montant non rapproché du document
get_payment_type(name, bank_account_head)-> "Debit" | "Credit"Sens de la ligne de rapprochement
set_clearance_date(name, date)-> NoneÉcrit ou efface la date de compensation
make_new_document(transactions)-> Document | NonePrépare le document du bouton Créer ; None ouvre un formulaire vide

L'objet self.context donne la ou les transactions sélectionnées (bank_transactions), le compte bancaire (bank_account), son compte comptable (bank_account_head), la société, la devise, le montant signé (amount, positif pour une entrée) et le sens (direction).

Forme d'un document candidat

get_candidates renvoie une liste de dictionnaires :

{
    "doctype": "Loan Repayment",
    "name": "LR-0001",
    "date": "2026-10-03",
    "amount": -120.0,              # signé, devise du compte bancaire ; négatif = sortie
    "unreconciled_amount": 120.0,  # montant encore rapprochable
    "currency": "EUR",
    "party": "SUP-0001",
    "party_name": "Banque Exemple",
    "reference_string": "LR-0001: contrat 42",
    # facultatif
    "reference_date": "2026-10-03",
    "mode_of_payment": "Virement",
    "score": 0,
    "match_reasons": [],
    "extra": {},                   # données libres pour vos colonnes et panneaux
}

Utilisez frappe.get_list : les permissions de l'utilisateur s'appliquent. Dokos retire aussi les lignes que l'utilisateur ne peut pas lire, et ignore un fournisseur dont is_permitted() renvoie False.

Exemple

# mon_app/rapprochement.py
import frappe
from erpnext.accounts.bank_reconciliation.provider import BankReconciliationProvider


class LoanRepaymentProvider(BankReconciliationProvider):
    # « Loan Repayment » est un DocType fictif de l'application, avec les champs
    # unreconciled_amount et clearance_date.
    doctype = "Loan Repayment"
    party_type = "Supplier"
    party_field = "lender"
    party_name_field = "lender_name"
    reference_field = "contract_number"
    reference_date_field = "due_date"
    directions = ("out",)
    columns = [{"field": "extra.contract_number", "label": "Contrat", "format": "Data"}]

    def get_candidates(self, filters):
        rows = frappe.get_list(
            "Loan Repayment",
            filters={
                "docstatus": 1,
                "company": self.context.company,
                "unreconciled_amount": ("!=", 0),
            },
            fields=["name", "due_date", "unreconciled_amount", "lender", "lender_name", "contract_number"],
        )
        return [
            {
                "doctype": self.doctype,
                "name": r.name,
                "date": r.due_date,
                "amount": -r.unreconciled_amount,
                "unreconciled_amount": r.unreconciled_amount,
                "currency": self.context.currency,
                "party": r.lender,
                "party_name": r.lender_name,
                "reference_string": f"{r.name}: {r.contract_number}",
                "extra": {"contract_number": r.contract_number},
            }
            for r in rows
        ]

Un onglet Loan Repayment apparaît dans le panneau des documents quand la transaction sélectionnée est une sortie d'argent.

bank_reconciliation_match_rules : classer les documents proposés

Une règle modifie le score (0 à 100) des documents candidats et explique pourquoi.

# mon_app/hooks.py
bank_reconciliation_match_rules = ["mon_app.rapprochement.numero_de_contrat"]
def numero_de_contrat(context, candidates):
    libelle = " ".join(t.get("description") or "" for t in context.bank_transactions)
    for c in candidates:
        numero = c.get("extra", {}).get("contract_number")
        if numero and numero in libelle:
            c["score"] = max(c.get("score", 0), 95)
            c.setdefault("match_reasons", []).append("Numéro de contrat trouvé dans le libellé")

Signature : fn(context, candidates) -> list[dict] | None. Renvoyez une nouvelle liste pour retirer des candidats, ou None pour garder la liste modifiée sur place.

Ordre d'exécution :

  1. Dokos applique ses règles natives : montant identique, historique des rapprochements, tiers, date la plus proche.
  2. Dokos appelle les règles des applications, dans l'ordre des hooks.
  3. Dokos trie les candidats par score, puis par date.

Présélection : si une règle d'application a changé un score, le premier document n'est présélectionné que si son score est d'au moins 80 et a au moins 10 points d'avance sur le suivant. Sinon, aucun document n'est présélectionné. Une règle propose, elle ne peut pas forcer la sélection. Si aucune règle d'application ne change de score, la présélection native reste inchangée.

Avec Afficher la liste complète, les règles ne servent qu'à remplir match_reasons : rien n'est retiré ni présélectionné.

auto_reconciliation_methods : rapprochement automatique

Le bouton de rapprochement automatique appelle chaque fonction de ce hook pour chaque transaction, avant la recherche native.

# mon_app/hooks.py
auto_reconciliation_methods = ["mon_app.rapprochement.par_mandat"]

Signature : fn(bank_transaction) -> bool | list[dict] | None.

Valeur renvoyéeEffet
TrueLa fonction a rapproché la transaction ; Dokos passe à la suivante
Liste de documents candidatsDokos les ajoute à sa propre recherche (ils peuvent donner une proposition à valider si plusieurs tiers sont concernés)
NoneRien trouvé ; Dokos continue normalement

Chaque transaction est traitée dans son propre point de sauvegarde : une exception annule seulement cette transaction, qui est comptée comme « laissée au rapprochement manuel ».

bank_reconciliation_batch_reconcilers : virements groupés

Après le traitement transaction par transaction, le rapprochement automatique appelle chaque fonction de ce hook avec toutes les transactions. Dokos y déclare lui-même le rapprochement des virements Stripe, GoCardless et Stancer.

# mon_app/hooks.py
bank_reconciliation_batch_reconcilers = ["mon_app.rapprochement.virements_prestataire"]

Signature : fn(bank_transactions: list) -> int | None. Renvoyez le nombre de transactions rapprochées : il est ajouté au résumé affiché à l'utilisateur. Chaque fonction tourne dans son propre point de sauvegarde ; une erreur n'empêche pas les suivantes.

bank_reconciliation_validators : refuser un rapprochement

Un validateur est appelé quand l'utilisateur clique sur Rapprocher, après les contrôles de Dokos (même tiers, même société…), et lors d'un rapprochement depuis un ordre de paiement.

# mon_app/hooks.py
bank_reconciliation_validators = ["mon_app.rapprochement.valider"]
import frappe
from frappe import _


def valider(context, documents):
    if any(d["doctype"] == "Loan Repayment" for d in documents) and len(context.bank_transactions) > 1:
        frappe.throw(_("Un remboursement de prêt se rapproche avec une seule transaction."))

Signature : fn(context, documents) -> None. Levez une erreur (frappe.throw) pour refuser : rien n'est enregistré et l'utilisateur voit le message.

bank_reconciliation_events : réagir à un rapprochement

Ces événements sont appelés par la Transaction bancaire à chaque ligne de rapprochement ajoutée (on_allocate) ou retirée (on_deallocate), quel que soit l'écran : rapprochement manuel ou automatique, ordre de paiement, prélèvement SEPA, annulation du rapprochement, annulation de la transaction.

# mon_app/hooks.py
bank_reconciliation_events = {
    "on_allocate": ["mon_app.rapprochement.marquer_paye"],
    "on_deallocate": ["mon_app.rapprochement.marquer_impaye"],
}
import frappe


def marquer_paye(bank_transaction, row):
    if row.payment_document != "Loan Repayment":
        return
    frappe.get_doc("Loan Repayment", row.payment_entry).set_status(update=True)
    # Appel externe : seulement après la validation de la transaction
    frappe.enqueue("mon_app.notifications.preteur", remboursement=row.payment_entry, enqueue_after_commit=True)

Signature : fn(bank_transaction, row) -> None. row est la ligne de la table Paiements de la transaction : payment_document, payment_entry, allocated_amount, reconciliation_type (« Matched » ou « Voucher Created » si le rapprochement a créé le document).

Les événements tournent dans la même transaction de base de données que le rapprochement. Une exception dans votre fonction bloque le rapprochement : tout est annulé et l'utilisateur voit votre message d'erreur. N'appelez jamais un service externe (HTTP, e-mail, autre site) directement : utilisez frappe.enqueue(..., enqueue_after_commit=True), pour que rien ne parte si le rapprochement est annulé.

bank_reconciliation_actions : ajouter une action

Une action s'affiche sous le bouton Rapprocher. Les rapprochements depuis un Ordre de paiement et depuis un Prélèvement SEPA utilisent ce même mécanisme.

# mon_app/hooks.py
bank_reconciliation_actions = ["mon_app.rapprochement.action_remboursement"]

La fonction déclarée renvoie la description de l'action, ou None pour la masquer :

import frappe
from frappe import _


def action_remboursement(context):
    if not frappe.has_permission("Loan Repayment", "write"):
        return None  # l'utilisateur n'a pas le droit : action masquée
    return {
        "name": "loan_repayment",
        "label": _("Rapprocher avec un remboursement de prêt"),
        "method": "mon_app.rapprochement.rapprocher_remboursement",
        "when": {"min_transactions": 1, "max_transactions": 1, "direction": "out", "needs_documents": False},
        "input": {
            "fieldtype": "Link",
            "options": "Loan Repayment",
            "filters": {"docstatus": 1},
            "prefill_method": "mon_app.rapprochement.deviner_remboursement",
        },
        "confirm": _("Une écriture de journal sera créée. Continuer ?"),
        "sort_order": 50,
    }


@frappe.whitelist(methods=["POST"])
def rapprocher_remboursement(bank_transactions, documents, value=None):
    frappe.has_permission("Loan Repayment", "write", throw=True)
    # ... rapprochement ...
    return {"reconciled": 1, "message": None, "open": None}
CléRôle
name, labelIdentifiant unique, libellé du bouton
methodMéthode @frappe.whitelist appelée avec bank_transactions, documents et value (valeur du champ)
whenConditions d'affichage : nombre de transactions, sens (in, out, any), documents requis
inputChamp Lien facultatif avant le bouton ; prefill_method(bank_transaction) -> str | None le pré-remplit
confirmMessage de confirmation facultatif

La méthode renvoie {"reconciled": int, "message": str | None, "open": {"doctype": ..., "name": ...} | None}.

Permissions : Dokos ne gère pas de liste de rôles pour les actions. Votre méthode doit vérifier elle-même les permissions. Masquer l'action (renvoyer None) évite seulement de la montrer à un utilisateur qui ne peut pas l'utiliser.

bank_reconciliation_transaction_columns : colonnes de transactions

# mon_app/hooks.py
bank_reconciliation_transaction_columns = ["mon_app.rapprochement.colonnes"]
def colonnes():
    # custom_categorie_banque : champ personnalisé de la Transaction bancaire
    return [{"field": "custom_categorie_banque", "label": "Catégorie de la banque", "format": "Data"}]

Signature : fn() -> list[dict]. Chaque colonne a field, label et facultativement format (Currency, Date, Data, Link), align, hidden, sortable. Le champ doit exister sur la Transaction bancaire (champ standard ou personnalisé) ; Dokos refuse les autres. Les colonnes du tableau des documents se déclarent dans l'attribut columns du fournisseur, avec extra.<clé> pour lire les données de extra.

bank_reconciliation_js : panneaux JavaScript

Pour afficher un bandeau, un panneau ou un badge, déclarez un bundle JavaScript de l'application. La page le charge avant d'afficher son interface.

# mon_app/hooks.py
bank_reconciliation_js = ["mon_app_rapprochement.bundle.js"]

Le bundle inscrit une extension dans le registre erpnext.bank_reconciliation.extensions :

// mon_app/public/js/mon_app_rapprochement.bundle.js
erpnext.bank_reconciliation.extensions.register({
    name: "mon_app.aide_contrat",
    sort_order: 50,
    slots: {
        "documents.footer": (el, ctx) => {
            const render = (c) => {
                el.textContent =
                    c.document_type === "Loan Repayment"
                        ? __("Le numéro de contrat dans le libellé sélectionne le remboursement.")
                        : "";
            };
            render(ctx);
            return { update: render };
        },
    },
    format_candidate(row) {
        if (row.doctype === "Loan Repayment" && row.score >= 95) row.party_name += " ★";
        return row;
    },
});
EmplacementPosition
page.headerAu-dessus des indicateurs de la page
transactions.toolbarBarre d'outils de la liste des transactions
documents.footerSous le tableau des documents
actions.extraSous le bouton Rapprocher

Une fonction d'emplacement reçoit l'élément HTML et le contexte ctx, et peut renvoyer { update(ctx), destroy() }. ctx contient le compte bancaire, la société, la devise, la période, les transactions et documents sélectionnés, l'onglet courant, la configuration, et deux fonctions : refresh() et reconcile(documents).

Les autres fonctions facultatives : format_candidate(row, ctx) modifie l'affichage d'un document ; can_reconcile(ctx) renvoie true ou un texte qui désactive Rapprocher et explique pourquoi.

La page émet aussi les événements reconciled et selection_change sur erpnext.bank_reconciliation (en plus de filter_change et refresh) :

erpnext.bank_reconciliation.on("reconciled", ({ bank_transactions, documents, result }) => { ... });
Les emplacements attendent une fonction, pas un composant Vue : chaque bundle embarque sa propre copie de Vue. Pour utiliser Vue, montez votre propre application dans la fonction (createApp(MonPanneau, { ctx }).mount(el)) et démontez-la dans destroy(). Une erreur dans une extension est interceptée et écrite dans la console du navigateur : la page et les autres extensions continuent de fonctionner.

Points d'attention

  • Les erreurs des fournisseurs, règles et validateurs ne sont pas interceptées : elles arrêtent l'opération en cours et l'utilisateur voit le message.
  • Les événements on_allocate et on_deallocate bloquent le rapprochement en cas d'erreur (voir plus haut).
  • En rapprochement automatique, une erreur annule seulement la transaction concernée.
  • Gardez les règles de score sans effet de bord : elles sont appelées à chaque changement de sélection.
  • Loan Repayment, contract_number et custom_categorie_banque sont des exemples fictifs, à créer dans votre application.