fix(workspace): insert importer link into ERPNext Invoicing Banking card

Replaces the standalone Bank Statements workspace (incorrect approach —
ERPNext doesn't expose top-level apps as tiles automatically) with a
runtime insertion of a single 'Bank Statement Importer' link into the
existing 'Banking' card on the ERPNext-owned Invoicing workspace.

Editing the workspace at runtime via after_migrate (not by overriding
its JSON) is intentional: ERPNext resyncs that JSON on every `bench
migrate`, so a file-level patch would be reverted. after_migrate runs
AFTER the resync, so the inserted link stays.

The insert is idempotent — it skips when the link is already present,
when the Banking card break isn't there, or when Bank Statement Importer
DocType doesn't exist yet.
This commit is contained in:
Ali 2026-05-18 12:24:53 +00:00
parent c6c1394af6
commit 408102117c
3 changed files with 69 additions and 166 deletions

View File

@ -3,6 +3,73 @@
import frappe
def add_importer_link_to_invoicing_workspace():
"""Insert a 'Bank Statement Importer' link into the existing 'Banking' card
on the ERPNext 'Invoicing' workspace.
This intentionally edits an ERPNext-owned workspace at runtime rather than
overriding its JSON file: ERPNext re-syncs the JSON on every `bench
migrate`, so any direct file edit would be reverted. We run from
after_migrate, which fires AFTER that resync, so this stays in place.
Idempotent runs every migrate but only inserts when the link is missing.
"""
ws_name = "Invoicing"
link_to = "Bank Statement Importer"
card_label = "Banking"
if not frappe.db.exists("Workspace", ws_name):
return
if not frappe.db.exists("DocType", link_to):
return
ws = frappe.get_doc("Workspace", ws_name)
if any(l.type == "Link" and l.link_to == link_to for l in ws.links):
return
# Find the position of the "Banking" card break.
banking_idx = None
for i, link in enumerate(ws.links):
if link.type == "Card Break" and (link.label or "").strip() == card_label:
banking_idx = i
break
if banking_idx is None:
# No Banking card break — bail out instead of appending in a random spot.
print(f"BI: '{card_label}' Card Break not found on '{ws_name}' workspace, skipping link insert")
return
# Find the next Card Break — our link goes right before it (end of Banking
# section). If Banking is the last card, append to the very end.
next_card_idx = len(ws.links)
for i in range(banking_idx + 1, len(ws.links)):
if ws.links[i].type == "Card Break":
next_card_idx = i
break
# Append a row (Frappe auto-assigns parent fields), then move it into place.
ws.append("links", {
"label": link_to,
"link_to": link_to,
"link_type": "DocType",
"type": "Link",
"onboard": 0,
"is_query_report": 0,
"hidden": 0,
})
new_row = ws.links.pop()
ws.links.insert(next_card_idx, new_row)
for i, l in enumerate(ws.links):
l.idx = i + 1
ws.flags.ignore_version = True
ws.flags.ignore_permissions = True
ws.save(ignore_permissions=True)
frappe.db.commit()
print(f"BI: added '{link_to}' link to '{card_label}' card on '{ws_name}' workspace")
def link_kb_bank_accounts_to_bi():
"""For every Bank Account that appears in Kapital Bank Settings.account_mappings,
set bank_integration_type='Kapital Bank Settings' / bank_integration='Kapital Bank Settings'

View File

@ -144,11 +144,13 @@ def after_migrate_combined():
rename_bip_purposes,
migrate_standard_field_codes_to_labels,
copy_excel_preset_into_bank_integration,
add_importer_link_to_invoicing_workspace,
)
link_kb_bank_accounts_to_bi()
rename_bip_purposes()
migrate_standard_field_codes_to_labels()
copy_excel_preset_into_bank_integration()
add_importer_link_to_invoicing_workspace()
except Exception as e:
print(f"BI: post-migrate sync failed: {e}")
# REMOVED: Asset categories creation moved to setup_wizard_complete hook

View File

@ -1,166 +0,0 @@
{
"app": "jey_erp",
"charts": [],
"content": "[{\"id\":\"bs_header_shortcuts\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"bs_shortcut_importer\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Bank Statement Importer\",\"col\":3}},{\"id\":\"bs_shortcut_bt\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Bank Transaction\",\"col\":3}},{\"id\":\"bs_shortcut_brt\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Bank Reconciliation Tool\",\"col\":3}},{\"id\":\"bs_spacer\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"bs_header_masters\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Setup &amp; Registry</b></span>\",\"col\":12}},{\"id\":\"bs_card_setup\",\"type\":\"card\",\"data\":{\"card_name\":\"Setup\",\"col\":4}},{\"id\":\"bs_card_registry\",\"type\":\"card\",\"data\":{\"card_name\":\"Registry\",\"col\":4}},{\"id\":\"bs_card_daily\",\"type\":\"card\",\"data\":{\"card_name\":\"Daily Use\",\"col\":4}}]",
"creation": "2026-05-18 00:00:00.000000",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
"is_hidden": 0,
"label": "Bank Statements",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Setup",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Statement Importer",
"link_count": 0,
"link_to": "Bank Statement Importer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank",
"link_count": 0,
"link_to": "Bank",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Account",
"link_count": 0,
"link_to": "Bank Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Daily Use",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Transaction",
"link_count": 0,
"link_to": "Bank Transaction",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Reconciliation Tool",
"link_count": 0,
"link_to": "Bank Reconciliation Tool",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Registry",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Counterparties (Customers)",
"link_count": 0,
"link_to": "Bank Integration Customer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Counterparties (Suppliers)",
"link_count": 0,
"link_to": "Bank Integration Supplier",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Purpose Keywords",
"link_count": 0,
"link_to": "Bank Integration Purpose",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2026-05-18 00:00:00.000000",
"modified_by": "Administrator",
"module": "Jey Erp",
"name": "Bank Statements",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 6.0,
"shortcuts": [
{
"color": "Blue",
"doc_view": "List",
"label": "Bank Statement Importer",
"link_to": "Bank Statement Importer",
"type": "DocType"
},
{
"color": "Green",
"doc_view": "List",
"label": "Bank Transaction",
"link_to": "Bank Transaction",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "",
"label": "Bank Reconciliation Tool",
"link_to": "Bank Reconciliation Tool",
"type": "DocType"
}
],
"title": "Bank Statements",
"type": "Workspace"
}