refactor: drop unused Account Mappings from Bank Statement Importer

Bank Account is chosen explicitly in the Import dialog before the
parser runs, so the IBAN→Bank Account child table never carried weight
in this flow. Unlike kapital_bank — where the API hands over raw IBANs
and needs a lookup table — jey_erp's Excel import fixes one Bank
Account per file from the dialog and applies it to every row.

Removed surface:
- Bank Statement Importer JSON: Account Mappings subtab (Mappings group)
  and Accounts subtab (Data group), plus their child table field
- Bank Integration Account Mapping DocType folder
- matching.py: match_similar_accounts
- creation.py: create_unmapped_accounts
- import_api.py: "accounts" branch of get_bi_reference_data_list
- bank_statement_importer.js: Match/Create buttons in the Accounts
  group, _bi_render_accounts renderer, and its load_tab line

Added post_model_sync patch drop_bank_integration_account_mapping that
deletes the DocType and its `tab<Name>` table from existing databases.
This commit is contained in:
Ali 2026-05-18 14:26:22 +00:00
parent 37dbc2be48
commit a7686eadc6
10 changed files with 27 additions and 270 deletions

View File

@ -204,74 +204,6 @@ def create_unmapped_suppliers(bank_integration):
return {"success": True, "created_count": created, "linked_count": linked} return {"success": True, "created_count": created, "linked_count": linked}
@frappe.whitelist()
def create_unmapped_accounts(bank_integration):
"""Create Bank Account records for unmapped account_mappings rows."""
import erpnext
bi = frappe.get_doc("Bank Statement Importer", bank_integration)
unmapped = [r for r in bi.account_mappings if not r.bank_account and r.iban]
if not unmapped:
return {"success": True, "created_count": 0, "message": "No unmapped accounts to create"}
default_bank = bi.default_bank
default_company = erpnext.get_default_company()
# Only need these for accounts that don't already exist as Bank Accounts.
if any(not frappe.db.exists("Bank Account", {"bank_account_no": r.iban}) for r in unmapped):
if not default_company:
frappe.throw(_("Set a Default Company in Global Defaults before creating Bank Accounts."))
if not default_bank:
frappe.throw(_("Set a Default Bank on the Bank Statement Importer before creating Bank Accounts."))
created = 0
for row in unmapped:
try:
existing = frappe.db.get_value("Bank Account", {"bank_account_no": row.iban}, "name")
if existing:
row.bank_account = existing
gl_account = frappe.db.get_value("Bank Account", existing, "account")
if gl_account:
row.gl_account = gl_account
frappe.db.set_value("Bank Account", existing, {
"bank_integration_type": "Bank Statement Importer",
"bank_integration": bi.name,
}, update_modified=False)
created += 1
continue
account_name = row.account_label or row.iban
ba = frappe.new_doc("Bank Account")
ba.account_name = account_name
ba.bank_account_no = row.iban
ba.iban = row.iban
ba.bank = default_bank
ba.company = default_company
ba.is_company_account = 1
ba.bank_integration_type = "Bank Statement Importer"
ba.bank_integration = bi.name
ba.insert(ignore_permissions=True)
row.bank_account = ba.name
gl_account = frappe.db.get_value("Bank Account", ba.name, "account")
if gl_account:
row.gl_account = gl_account
created += 1
except Exception as e:
frappe.log_error(
f"create_unmapped_accounts error for {row.iban}: {e}",
"BI Account Creation",
)
if created > 0:
bi.save(ignore_permissions=True)
frappe.db.commit()
return {"success": True, "created_count": created}
@frappe.whitelist() @frappe.whitelist()
def add_unmapped_to_table(bank_integration, table): def add_unmapped_to_table(bank_integration, table):
"""Add Bank Integration Customer/Supplier/Purpose registry rows (status=New) into the appropriate child table. """Add Bank Integration Customer/Supplier/Purpose registry rows (status=New) into the appropriate child table.

View File

@ -254,26 +254,15 @@ def _upsert_purpose(purpose, drcr, bank_integration):
@frappe.whitelist() @frappe.whitelist()
def get_bi_reference_data_list(bank_integration, data_type, limit=100, offset=0): def get_bi_reference_data_list(bank_integration, data_type, limit=100, offset=0):
"""Paginated rows for the Bank Integration "Data" tab subtabs. """Paginated rows for the Bank Statement Importer "Data" tab subtabs.
data_type {accounts, customers, suppliers, purposes}. data_type {customers, suppliers, purposes}.
""" """
limit = cint(limit) limit = cint(limit)
offset = cint(offset) offset = cint(offset)
try: try:
if data_type == "accounts": if data_type == "customers":
total = frappe.db.count("Bank Integration Account Mapping", filters={
"parent": bank_integration, "parenttype": "Bank Statement Importer"
})
data = frappe.get_all(
"Bank Integration Account Mapping",
filters={"parent": bank_integration, "parenttype": "Bank Statement Importer"},
fields=["name", "iban", "account_label", "currency", "bank_account", "gl_account"],
limit=limit, start=offset, order_by="idx asc",
)
elif data_type == "customers":
total = frappe.db.count("Bank Integration Customer", filters={"parent_bank_integration": bank_integration}) total = frappe.db.count("Bank Integration Customer", filters={"parent_bank_integration": bank_integration})
data = frappe.get_all( data = frappe.get_all(
"Bank Integration Customer", "Bank Integration Customer",

View File

@ -137,43 +137,6 @@ def match_similar_suppliers(bank_integration):
return {"success": True, "matched_count": matched, "total_processed": len(to_process)} return {"success": True, "matched_count": matched, "total_processed": len(to_process)}
@frappe.whitelist()
def match_similar_accounts(bank_integration):
"""Match account_mappings IBANs to ERPNext Bank Account by IBAN."""
bi = frappe.get_doc("Bank Statement Importer", bank_integration)
erp_bank_accounts = frappe.get_all("Bank Account", fields=["name", "bank_account_no", "iban", "account"])
iban_index = {}
for ba in erp_bank_accounts:
if ba.iban:
iban_index[ba.iban.strip()] = ba
if ba.bank_account_no:
iban_index[ba.bank_account_no.strip()] = ba
matched = 0
total_processed = 0
for row in bi.account_mappings:
if row.bank_account or not row.iban:
continue
total_processed += 1
match = iban_index.get(row.iban.strip())
if match:
row.bank_account = match.name
if match.account:
row.gl_account = match.account
# Also set the BA's bank_integration link
frappe.db.set_value("Bank Account", match.name, {
"bank_integration_type": "Bank Statement Importer",
"bank_integration": bi.name,
}, update_modified=False)
matched += 1
if matched > 0:
bi.save(ignore_permissions=True)
frappe.db.commit()
return {"success": True, "matched_count": matched, "total_processed": total_processed}
def _set_mapping_row(bi, table_field, key_field, key_value, values): def _set_mapping_row(bi, table_field, key_field, key_value, values):
"""Update an existing child row matching key_field=key_value, or append new.""" """Update an existing child row matching key_field=key_value, or append new."""
for row in bi.get(table_field): for row in bi.get(table_field):

View File

@ -1,63 +0,0 @@
{
"actions": [],
"creation": "2026-05-08 00:00:00.000000",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"iban",
"account_label",
"currency",
"bank_account",
"gl_account"
],
"fields": [
{
"fieldname": "iban",
"fieldtype": "Data",
"in_list_view": 1,
"label": "IBAN",
"reqd": 1
},
{
"fieldname": "account_label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Account Label"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "bank_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Bank Account",
"options": "Bank Account"
},
{
"fieldname": "gl_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "GL Account",
"options": "Account"
}
],
"allow_bulk_edit": 1,
"editable_grid": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-05-08 00:00:00.000000",
"modified_by": "Administrator",
"module": "Jey Erp",
"name": "Bank Integration Account Mapping",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -1,8 +0,0 @@
# Copyright (c) 2026, JeyERP and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class BankIntegrationAccountMapping(Document):
pass

View File

@ -31,27 +31,6 @@ frappe.ui.form.on('Bank Statement Importer', {
_show_load_data_dialog(frm); _show_load_data_dialog(frm);
}); });
// === Accounts group ===
frm.add_custom_button(__('Match accounts to Bank Accounts'), () => {
_safe(frm, () => _call(frm, 'jey_erp.bank_integration.matching.match_similar_accounts',
{ bank_integration: frm.doc.name },
r => frappe.show_alert({
message: __('Matched: {0} of {1}', [r.matched_count, r.total_processed]),
indicator: 'green',
}, 5),
));
}, __('Accounts'));
frm.add_custom_button(__('Create Bank Accounts'), () => {
_safe(frm, () => _call(frm, 'jey_erp.bank_integration.creation.create_unmapped_accounts',
{ bank_integration: frm.doc.name },
r => frappe.show_alert({
message: __('Created: {0}', [r.created_count]),
indicator: 'green',
}, 5),
));
}, __('Accounts'));
// === Customers group === // === Customers group ===
frm.add_custom_button(__('Match customers by similar name'), () => { frm.add_custom_button(__('Match customers by similar name'), () => {
_safe(frm, () => _call(frm, 'jey_erp.bank_integration.matching.match_similar_customers', _safe(frm, () => _call(frm, 'jey_erp.bank_integration.matching.match_similar_customers',
@ -498,7 +477,6 @@ function _show_load_data_dialog(frm) {
function _bi_load_data_tabs(frm) { function _bi_load_data_tabs(frm) {
const name = frm.doc.name; const name = frm.doc.name;
_bi_load_tab(name, 'accounts', 'bi-accounts-container', _bi_render_accounts);
_bi_load_tab(name, 'customers', 'bi-customers-container', _bi_render_customers); _bi_load_tab(name, 'customers', 'bi-customers-container', _bi_render_customers);
_bi_load_tab(name, 'suppliers', 'bi-suppliers-container', _bi_render_suppliers); _bi_load_tab(name, 'suppliers', 'bi-suppliers-container', _bi_render_suppliers);
_bi_load_tab(name, 'purposes', 'bi-purposes-container', _bi_render_purposes); _bi_load_tab(name, 'purposes', 'bi-purposes-container', _bi_render_purposes);
@ -520,28 +498,6 @@ function _bi_load_tab(bank_integration, data_type, container_id, renderer) {
}); });
} }
function _bi_render_accounts(rows, total, reload) {
let html = _bi_list_header(__('Accounts'), total, 'bi-refresh-accounts');
if (rows && rows.length) {
html += `<table class="table table-bordered" style="font-size:13px;">
<thead class="grid-heading-row"><tr>
<th>${__('IBAN')}</th><th>${__('Label')}</th><th>${__('Currency')}</th>
<th>${__('Bank Account')}</th><th>${__('GL Account')}</th>
</tr></thead><tbody>`;
rows.forEach(a => {
const ba = a.bank_account ? `<a href="/app/bank-account/${encodeURIComponent(a.bank_account)}">${a.bank_account}</a>` : '-';
const gl = a.gl_account ? `<a href="/app/account/${encodeURIComponent(a.gl_account)}">${a.gl_account}</a>` : '-';
html += `<tr><td>${a.iban || '-'}</td><td>${frappe.utils.escape_html(a.account_label || '-')}</td>
<td>${a.currency || '-'}</td><td>${ba}</td><td>${gl}</td></tr>`;
});
html += '</tbody></table>';
} else {
html += _bi_empty_state(__('No account mappings yet. Add rows in the Mappings tab.'));
}
$('#bi-accounts-container').html(html);
$('#bi-refresh-accounts').on('click', reload);
}
function _bi_render_customers(rows, total, reload) { function _bi_render_customers(rows, total, reload) {
let html = _bi_list_header(__('Customers'), total, 'bi-refresh-customers'); let html = _bi_list_header(__('Customers'), total, 'bi-refresh-customers');
if (rows && rows.length) { if (rows && rows.length) {

View File

@ -37,8 +37,6 @@
"column_mappings", "column_mappings",
"data_tab", "data_tab",
"data_tab_html", "data_tab_html",
"accounts_tab",
"accounts_list_html",
"customers_tab", "customers_tab",
"customers_list_html", "customers_list_html",
"suppliers_tab", "suppliers_tab",
@ -47,9 +45,6 @@
"purposes_list_html", "purposes_list_html",
"mappings_tab", "mappings_tab",
"mappings_tab_html", "mappings_tab_html",
"account_mappings_tab",
"account_mappings_section",
"account_mappings",
"customer_mappings_tab", "customer_mappings_tab",
"customer_mappings_section", "customer_mappings_section",
"customer_mappings", "customer_mappings",
@ -256,18 +251,6 @@
"fieldname": "data_tab_html", "fieldname": "data_tab_html",
"fieldtype": "HTML" "fieldtype": "HTML"
}, },
{
"fieldname": "accounts_tab",
"fieldtype": "Tab Break",
"js_parent_subtab": "Data",
"label": "Accounts"
},
{
"fieldname": "accounts_list_html",
"fieldtype": "HTML",
"label": "Accounts List",
"options": "<div id=\"bi-accounts-container\"><div class=\"text-center text-muted\" style=\"padding:20px;\">No data yet. Accounts populate after the first Excel import.</div></div>"
},
{ {
"fieldname": "customers_tab", "fieldname": "customers_tab",
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
@ -313,24 +296,6 @@
"fieldname": "mappings_tab_html", "fieldname": "mappings_tab_html",
"fieldtype": "HTML" "fieldtype": "HTML"
}, },
{
"fieldname": "account_mappings_tab",
"fieldtype": "Tab Break",
"js_parent_subtab": "Mappings",
"label": "Account Mappings"
},
{
"fieldname": "account_mappings_section",
"fieldtype": "Section Break",
"label": "Account Mappings"
},
{
"allow_bulk_edit": 1,
"fieldname": "account_mappings",
"fieldtype": "Table",
"label": "Account Mappings",
"options": "Bank Integration Account Mapping"
},
{ {
"fieldname": "customer_mappings_tab", "fieldname": "customer_mappings_tab",
"fieldtype": "Tab Break", "fieldtype": "Tab Break",

View File

@ -10,4 +10,5 @@ jey_erp.patches.rename_bank_integration_to_importer
jey_erp.patches.create_asset_categories_post_migration jey_erp.patches.create_asset_categories_post_migration
jey_erp.patches.remove_old_won_lost_fields jey_erp.patches.remove_old_won_lost_fields
jey_erp.patches.remove_won_lost_breaks jey_erp.patches.remove_won_lost_breaks
jey_erp.patches.remove_won_lost_checkboxes jey_erp.patches.remove_won_lost_checkboxes
jey_erp.patches.drop_bank_integration_account_mapping

View File

@ -0,0 +1,22 @@
"""Drop the Bank Integration Account Mapping DocType.
The child table was unused in the Bank Statement Importer flow: Bank Account
is picked explicitly in the Import dialog, so there was no need for an
IBANBank Account lookup table. The kapital_bank app keeps its own
equivalent because that flow pulls transactions through an API without a
per-import Bank Account choice.
Idempotent runs only when the DocType is still around.
"""
import frappe
def execute():
dt = "Bank Integration Account Mapping"
if not frappe.db.exists("DocType", dt):
return
# Drops both the metadata row and the underlying `tab<Name>` table.
frappe.delete_doc("DocType", dt, ignore_permissions=True, force=True)
frappe.db.commit()
print(f"BI: dropped DocType '{dt}'")