20 KiB
Bank Integration in jey_erp — Implementation Plan
Created: 2026-05-08 Status: Approved, not yet started Owner: Ali (jeysoft.mmc@gmail.com)
Context
We're building a universal "Bank Integration" tool inside jey_erp for importing bank statements from Excel files. It complements the existing kapital_bank app (which integrates with the BIRBank B2B API). The new tool targets banks that don't expose an API — clients export Excel statements from their internet-banking and we import them via column mapping presets.
The motivation for putting it in jey_erp (rather than a new app per bank): the user expects to support many banks over time, and a single shared dropdown Import From... on the Bank Transaction list is cleaner than each bank's app injecting its own button group.
The kapital_bank app stays mostly untouched — only its buttons move into the new shared Import From... group, and its BRT-extension JS is removed (the functionality moves to jey_erp as a universal version that works with both Bank Integration mappings and Kapital Bank Settings mappings).
Architectural Decisions
| Aspect | Decision | Reasoning |
|---|---|---|
| Settings shape | Multi-record DocType Bank Integration (not Single) |
Future-proofs for multiple banks. Initial focus is one bank, but structure is ready. |
| Coexistence with kapital_bank | kapital_bank kept as-is; only buttons move to new group; BRT-patch removed and replaced by universal version in jey_erp | "Don't refactor kb" was an explicit constraint. Each bank can have its own dedicated app/doctypes if needed. |
| Outbound payments | NOT covered by Bank Integration | Excel banks have no API; outbound stays kb-only. |
| Excel format | Hardcoded standard fields + flexible column mapping per bank (presets) | User maps their bank's columns to our standard fields. Popular banks get fixture presets (ABB, Pasha, Custom). |
| Import scope | BT-only (no PE/JE creation at import time) | Mappings are applied later in BRT via "Create & Reconcile". This matches kb's BT-list import flow (import_bulk_bank_transactions). |
| Two-step registry flow | Yes, like kb — registries fill at import time (option A from discussion) | Lets user run "Match similar customers/suppliers" before reconciliation. Avoids preview-leakage of half-mapped data. |
| Tracking doctype | NOT in iteration 1 | Deduplication via direct query on Bank Transaction.reference_number + bank_account. Cleanup hooks rely on standard ERPNext relationships. Add later if cases emerge. |
| Bank Account ↔ Bank Integration link | Hidden read-only Dynamic Link on Bank Account, set by user in BRT |
One source of truth on the BA, points to either a Bank Integration record or Kapital Bank Settings (Single). Allows resolving mappings for either system uniformly. |
| BRT mapping selection UX | Explicit select after Bank Account, with default loaded from BA's hidden field, written back on confirm | Trades one extra click for zero ambiguity. Default makes repeat use frictionless. |
| Fixtures vs default_data | Fixtures (fixtures/bank_integration_excel_preset.json) |
Presets are global, not per-company; standard Frappe fixture loader fits. |
Data Flow
[Excel file] ─→ Excel Parser (driven by Bank Integration Excel Preset)
│
▼
[Preview dialog]
│
▼ user selects txns + clicks Import
│
┌────────────┴────────────┐
▼ ▼
[Bank Transaction] [Bank Integration Customer / Supplier / Purpose]
(status=New, awaiting manual mapping)
│
▼
Bank Integration Settings tabs
│
user clicks "Match similar" / "Create unmapped"
│
▼
Customer/Supplier/Transaction Mappings populated
│
▼
BRT (Bank Reconciliation Tool):
user picks Bank Account
→ default Bank Integration loaded from BA hidden field
→ user confirms / changes the select
→ selects BTs → "Create & Reconcile"
│
▼
mapping_resolver returns mappings
(from Bank Integration record OR Kapital Bank Settings,
based on Bank Account.bank_integration_type)
│
▼
Payment Entry / Journal Entry created and reconciled with BT
File Structure
jey_erp/
├── jey_erp/
│ ├── doctype/ # standard Frappe location
│ │ ├── bank_integration/
│ │ │ ├── bank_integration.json
│ │ │ ├── bank_integration.py
│ │ │ └── bank_integration.js
│ │ ├── bank_integration_account_mapping/
│ │ ├── bank_integration_customer_mapping/
│ │ ├── bank_integration_supplier_mapping/
│ │ ├── bank_integration_transaction_mapping/
│ │ ├── bank_integration_customer/ # registry
│ │ ├── bank_integration_supplier/ # registry
│ │ ├── bank_integration_purpose/ # registry
│ │ ├── bank_integration_excel_preset/
│ │ └── bank_integration_excel_column_mapping/
│ │
│ ├── bank_integration/ # python logic (separate from doctype/)
│ │ ├── __init__.py
│ │ ├── excel_parser.py # parse xlsx by preset
│ │ ├── import_api.py # whitelisted: bulk_import_bt + helpers
│ │ ├── matching.py # match_similar_customers/suppliers/accounts
│ │ ├── creation.py # create_unmapped_customers/suppliers/accounts
│ │ ├── mapping_resolver.py # abstract: resolve mappings by Bank Account
│ │ ├── create_reconcile.py # universal "Create & Reconcile" backend
│ │ └── doc_events.py # on_trash/on_cancel hooks for PE/JE/BT
│ │
│ ├── public/js/
│ │ ├── bank_integration_form.js # buttons on Bank Integration form
│ │ ├── bank_transaction_list.js # NEW: "Import From..." dropdown with "Load from Excel"
│ │ └── bank_reconciliation_tool.js # ENHANCED: select + Create&Reconcile toolbar
│ │
│ ├── fixtures/
│ │ └── bank_integration_excel_preset.json # ABB / Pasha / Custom presets
│ │
│ ├── custom_fields.py # ADD: bank_integration_type/bank_integration on BA
│ └── hooks.py # ADD: doctype_js, doctype_list_js, doc_events
Doctype Schemas
Bank Integration (main, multi-record)
A close mirror of Kapital Bank Settings but as a regular DocType (one record per bank), and without is_active / default_login / card mappings.
Fields:
bank_name(Data, reqd) — display name e.g. "Pasha Bank", "ABB"- Tab "Settings":
default_company(Link → Company)default_bank(Link → Bank)similarity_threshold_customers(Percent, default 90)similarity_threshold_suppliers(Percent, default 80)similarity_threshold_purpose(Percent, default 70)consider_azeri_chars(Check, default 1)default_supplier_group/default_customer_group/default_payment_terms/default_territory(Link)
- Tab "Data" → subtabs Accounts / Customers / Suppliers / Purposes (HTML render with pagination, like kb)
- Tab "Mappings" → subtabs Account / Customer / Supplier / Transaction Mappings (child tables)
Child tables (mirror of kb-side)
Bank Integration Account Mapping: iban, account_label, currency (Link → Currency), bank_account (Link → Bank Account), gl_account (Link → Account)
Bank Integration Customer Mapping: bi_customer_name (Link → Bank Integration Customer), tax_id, erp_customer (Link → Customer), customer_group, territory, payment_terms, mapping_type (Select: Manual/Automatic)
Bank Integration Supplier Mapping: analogous to customer
Bank Integration Transaction Mapping: document_type (Select: Payment Entry/Journal Entry, default PE), purpose_keyword (Link → Bank Integration Purpose), payment_type (Select: Pay/Receive), counterparty_type (Select: Customer/Supplier), counterparty (Dynamic Link → counterparty_type), paid_from (Link → Account), paid_to (Link → Account), cost_center, multi_currency (Check), currency (Link → Currency), notes
Registries (top-level doctypes)
Bank Integration Customer: customer_name (Data, reqd), tax_id, iban, bank_code, status (Select: New/Mapped, default New), mapped_customer (Link → Customer), parent_bank_integration (Link → Bank Integration, makes registries per-bank)
Bank Integration Supplier: analogous
Bank Integration Purpose: purpose_keyword (Data, reqd), direction (Select: Pay/Receive/Both), status (Select: New/Mapped), parent_bank_integration (Link → Bank Integration)
Excel preset
Bank Integration Excel Preset:
preset_name(Data, reqd, unique)description(Small Text)header_row(Int, default 1) — 1-indexed row containing column headersskip_rows_after_header(Int, default 0)date_format(Data, default "%Y-%m-%d") — Python strftime formatamount_mode(Select: "Single column with sign" / "Separate debit/credit columns" / "Single column + direction column", default "Separate debit/credit columns")column_mappings(Table → Bank Integration Excel Column Mapping)
Bank Integration Excel Column Mapping: excel_column (Data — header text in Excel), standard_field (Select: date/reference_number/counterparty/counterparty_tax_id/counterparty_iban/amount/debit/credit/direction/currency/purpose/description), notes
Custom Fields on Bank Account
Added in jey_erp/custom_fields.py (existing create_custom_fields()):
{
"doctype": "Bank Account",
"fieldname": "bank_integration_type",
"fieldtype": "Select",
"label": "Bank Integration Type",
"options": "\nBank Integration\nKapital Bank Settings",
"hidden": 1, "read_only": 1,
"insert_after": "bank_account_no"
},
{
"doctype": "Bank Account",
"fieldname": "bank_integration",
"fieldtype": "Dynamic Link",
"label": "Bank Integration",
"options": "bank_integration_type",
"hidden": 1, "read_only": 1,
"insert_after": "bank_integration_type"
}
The Dynamic Link resolves to either Bank Integration records or the Kapital Bank Settings Single (whose name is just "Kapital Bank Settings").
Backend Modules
bank_integration/excel_parser.py
parse_excel(file_url, preset_name) → list[dict]- Uses
openpyxl(already a Frappe dependency) - Returns dicts in standard form:
{ref_no, date, counterparty, contr_voen, contr_iban, amount, currency, drcr, purpose, description} - Handles 3 amount modes: single signed column / separate debit+credit / amount+direction column
bank_integration/import_api.py
@whitelist parse_excel_for_preview(file_url, preset_name, bank_integration)— parses and returns preview, also dedupes against existing BT byreference_number + bank_account@whitelist import_bulk_bt(txn_list, bank_integration, bank_account)→ enqueues background job_process_bulk_bt_import(txn_list, bank_integration, bank_account, user):- For each txn:
- Upsert Bank Integration Customer/Supplier (status=New if new) — only when counterparty present
- Upsert Bank Integration Purpose
- Insert + submit Bank Transaction (party left empty — filled later via Create&Reconcile)
frappe.publish_realtimeevents:bi_bt_import_progress/bi_bt_import_complete
- For each txn:
bank_integration/matching.py
Mirrors kb: match_similar_customers, match_similar_suppliers, match_similar_accounts. Uses SequenceMatcher + a normalize helper (Azerbaijani translit). Consider extracting _normalize and _AZERI_MAP to a shared place if both apps end up using it.
bank_integration/creation.py
Mirrors kb: create_unmapped_customers, create_unmapped_suppliers, create_unmapped_accounts — creates ERPNext entities and updates the corresponding mapping rows.
bank_integration/mapping_resolver.py (key abstraction)
def resolve_mappings_for_bank_account(bank_account_name):
"""Returns dict: {transaction_mappings, customer_mappings,
supplier_mappings, settings} or None if not configured.
Reads from Bank Integration record or Kapital Bank Settings,
depending on bank_integration_type field on Bank Account."""
ba = frappe.get_doc("Bank Account", bank_account_name)
if ba.bank_integration_type == "Bank Integration" and ba.bank_integration:
bi = frappe.get_doc("Bank Integration", ba.bank_integration)
return {...build from bi child tables...}
elif ba.bank_integration_type == "Kapital Bank Settings":
kb = frappe.get_single("Kapital Bank Settings")
return {...build from kb child tables...}
return None
bank_integration/create_reconcile.py
Backend for the universal "Create & Reconcile" button. Pulls logic from kb's existing _import_one_transaction (the PE/JE creation arm) but generalised through mapping_resolver. Operates on already-existing Bank Transactions selected by user.
bank_integration/doc_events.py
on_trash_bank_transaction/on_cancel_bank_transaction— initial implementation: log + sanity checks. No tracking doctype yet, so no cascading deletes.
Frontend Modules
public/js/bank_transaction_list.js (new)
- Hooks into
frappe.listview_settings['Bank Transaction'].onload - Creates
Import From... ▼button group with itemLoad from Excel - Click flow: dialog → choose Excel Preset → choose Bank Account → upload file → call
parse_excel_for_preview→ preview table (mirror of kb'sshowTransactionSelection) → click Import → enqueue + realtime progress + final summary
public/js/bank_reconciliation_tool.js (extend existing)
- Keep current "Bank Transaction column" patch (already in jey_erp)
- Add
checkboxColumn: trueto DataTable rebuild (port from kb) - Add a
Bank Integrationselect to the BRT header that fills from allBank Integrationrecords, and additionally adds aKapital Bankoption if kb is installed - Default value: read from
Bank Account.bank_integration_type / .bank_integrationviafrappe.db.get_valuewhen user picks a Bank Account - On change: call whitelist
update_bank_account_default_bi(bank_account, type, name)to persist to BA's hidden fields - Floating toolbar
Create & Reconcilebutton → callscreate_reconcile.create_documents_for_bts(bt_names, bank_account)
public/js/bank_integration_form.js (new)
A near-copy of kapital_bank_settings.js button setup — Match similar / Create matching / Add unmapped buttons under Accounts / Customers / Suppliers / Purposes groups.
Fixtures
jey_erp/fixtures/bank_integration_excel_preset.json — initial set:
- ABB Standard Statement (column mappings for ABB internet-banking export)
- Pasha Business Statement
- Custom Template (empty
column_mappings— user fills in dialog)
Register in hooks.py:
fixtures = [
{"doctype": "Print Format"},
{"doctype": "Bank Integration Excel Preset"},
{"doctype": "Bank Integration Excel Column Mapping"},
]
hooks.py Changes (jey_erp)
doctype_list_js = {
...
"Bank Transaction": "public/js/bank_transaction_list.js",
}
# bank_reconciliation_tool.js already registered — file is extended in place
doc_events = {
...
"Bank Transaction": {
"on_trash": "jey_erp.bank_integration.doc_events.on_trash_bank_transaction",
"on_cancel": "jey_erp.bank_integration.doc_events.on_cancel_bank_transaction",
},
}
In custom_fields.py add the Bank Account block above. Ensure after_migrate_combined() calls the field creator (already does, via existing create_custom_fields).
Changes in kapital_bank (minimal)
-
client/bank_transaction.js— replace:listview.page.add_inner_button(__('Import'), ..., __('Kapital Bank'));with:
listview.page.add_inner_button(__('Load from Kapital Bank'), ..., __('Import From...')); -
client/payment_entry.js— same change. -
client/bank_reconciliation_tool.js— delete the file. -
hooks.py— remove"Bank Reconciliation Tool": "client/bank_reconciliation_tool.js"fromdoctype_js. -
No setup.py change in kb itself. Migration of existing kb-mapped Bank Accounts (proper
bank_integration_type/bank_integrationvalues for them) happens in jey_erp'safter_migrate: walkKapital Bank Account Mapping.bank_accountrows and set the BA's hidden fields.
Implementation Phases
| # | Phase | Depends on |
|---|---|---|
| 1 | Create all 10 Doctypes — JSON schemas + __init__.py + empty .py controllers |
— |
| 2 | Custom fields on Bank Account via custom_fields.py |
— |
| 3 | excel_parser.py + smoke test with one sample.xlsx |
1 |
| 4 | import_api.py + JS preview dialog + bulk import → creates BT |
1, 2, 3 |
| 5 | bank_transaction_list.js — "Load from Excel" item under "Import From..." |
4 |
| 6 | bank_integration_form.js + matching.py + creation.py (Settings buttons) |
1 |
| 7 | mapping_resolver.py — abstraction over the two mapping sources |
1 |
| 8 | create_reconcile.py — backend for universal Create&Reconcile |
7 |
| 9 | bank_reconciliation_tool.js — extension: checkboxColumn + select + toolbar |
8 |
| 10 | Migration script in jey_erp after_migrate: walk Kapital Bank Account Mappings → set bank_integration_type/name on their Bank Accounts |
2 |
| 11 | Minimal kapital_bank edits: rename buttons + delete BRT file + drop hook | 1–9 ready |
| 12 | Fixtures: bank_integration_excel_preset.json (ABB + Pasha + Custom) |
1 |
| 13 | End-to-end test: ABB Excel → BT → Settings (mappings) → BRT (Create&Reconcile) → PE/JE | all done |
Out of Scope (Iteration 1)
- Tracking doctype (
Bank Integration Transaction) — dedupe via directBank Transaction.reference_number + bank_accountquery. Add later if cleanup-cascade cases emerge. - Outbound payments (Send/Check/Cancel) — explicitly excluded; outbound stays kb-specific.
- Card statements support — Excel banks usually only provide account statements. Can be added as a preset mode if needed.
- Other BRT enhancements beyond Create & Reconcile.
- Sharing
_normalize/_AZERI_MAPbetween apps — duplicate for now, refactor if both apps continue to evolve in this area.
Notes for Future Self
kapital_bankhas TWO import paths (visible inbank_api.py):import_bulk_transactions(creates PE/JE/BT through full mappings, called fromclient/payment_entry.js) andimport_bulk_bank_transactions(creates only BT, called fromclient/bank_transaction.js). Both are active and not commented out. Our new tool mirrors the BT-only path.- BRT in BRT — the data table is rebuilt by
kapital_bank/client/bank_reconciliation_tool.jsto add a checkbox column. The currentjey_erp/public/js/bank_reconciliation_tool.jsadds the "Bank Transaction" column. After this work, jey_erp owns the entire BRT extension, and kb owns nothing on BRT. - The Dynamic Link trick on Bank Account avoids needing kb to be aware of jey_erp at all. The mapping resolver in jey_erp reads from kb Settings directly when the type is
Kapital Bank Settings. - Fixture-driven Excel presets means adding support for a new bank = editing one JSON file in jey_erp + running migrate.