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:
parent
37dbc2be48
commit
a7686eadc6
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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": []
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
IBAN→Bank 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}'")
|
||||||
Loading…
Reference in New Issue