# 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 headers - `skip_rows_after_header` (Int, default 0) - `date_format` (Data, default "%Y-%m-%d") — Python strftime format - `amount_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()`): ```python { "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 by `reference_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: 1. Upsert Bank Integration Customer/Supplier (status=New if new) — only when counterparty present 2. Upsert Bank Integration Purpose 3. Insert + submit Bank Transaction (party left empty — filled later via Create&Reconcile) - `frappe.publish_realtime` events: `bi_bt_import_progress` / `bi_bt_import_complete` ### `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) ```python 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 item `Load from Excel` - Click flow: dialog → choose Excel Preset → choose Bank Account → upload file → call `parse_excel_for_preview` → preview table (mirror of kb's `showTransactionSelection`) → 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: true` to DataTable rebuild (port from kb) - Add a `Bank Integration` select to the BRT header that fills from all `Bank Integration` records, and additionally adds a `Kapital Bank` option if kb is installed - Default value: read from `Bank Account.bank_integration_type / .bank_integration` via `frappe.db.get_value` when 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 & Reconcile` button → calls `create_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`: ```python fixtures = [ {"doctype": "Print Format"}, {"doctype": "Bank Integration Excel Preset"}, {"doctype": "Bank Integration Excel Column Mapping"}, ] ``` --- ## hooks.py Changes (jey_erp) ```python 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) 1. **`client/bank_transaction.js`** — replace: ```js listview.page.add_inner_button(__('Import'), ..., __('Kapital Bank')); ``` with: ```js listview.page.add_inner_button(__('Load from Kapital Bank'), ..., __('Import From...')); ``` 2. **`client/payment_entry.js`** — same change. 3. **`client/bank_reconciliation_tool.js`** — **delete the file**. 4. **`hooks.py`** — remove `"Bank Reconciliation Tool": "client/bank_reconciliation_tool.js"` from `doctype_js`. 5. **No setup.py change in kb itself.** Migration of existing kb-mapped Bank Accounts (proper `bank_integration_type`/`bank_integration` values for them) happens in jey_erp's `after_migrate`: walk `Kapital Bank Account Mapping.bank_account` rows 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 direct `Bank Transaction.reference_number + bank_account` query. 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_MAP` between apps — duplicate for now, refactor if both apps continue to evolve in this area. --- ## Notes for Future Self - `kapital_bank` has TWO import paths (visible in `bank_api.py`): `import_bulk_transactions` (creates PE/JE/BT through full mappings, called from `client/payment_entry.js`) and `import_bulk_bank_transactions` (creates only BT, called from `client/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.js` to add a checkbox column. The current `jey_erp/public/js/bank_reconciliation_tool.js` adds 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.