diff --git a/jey_erp/bank_integration/import_api.py b/jey_erp/bank_integration/import_api.py index 3e02ec8..06bb0d4 100644 --- a/jey_erp/bank_integration/import_api.py +++ b/jey_erp/bank_integration/import_api.py @@ -4,7 +4,7 @@ import json import frappe from frappe import _ -from frappe.utils import flt +from frappe.utils import cint, flt from jey_erp.bank_integration.excel_parser import parse_excel @@ -48,8 +48,14 @@ def parse_excel_for_preview(file_url, preset_name, bank_integration, bank_accoun @frappe.whitelist() -def import_bulk_bt(txn_list, bank_integration, bank_account): - """Enqueue bulk Bank Transaction creation as a background job.""" +def import_bulk_bt(txn_list, bank_integration, bank_account, load_registries=1): + """Enqueue bulk Bank Transaction creation as a background job. + + load_registries: when truthy, also upsert counterparties (→ Bank Integration + Customer/Supplier by direction) and purpose keywords (→ Bank Integration + Purpose) with status=New, exactly as before. When falsy, only Bank + Transactions are created. + """ if isinstance(txn_list, str): txn_list = json.loads(txn_list) @@ -61,6 +67,7 @@ def import_bulk_bt(txn_list, bank_integration, bank_account): bank_integration=bank_integration, bank_account=bank_account, user=user, + load_registries=cint(load_registries), queue="default", timeout=600, ) @@ -68,8 +75,8 @@ def import_bulk_bt(txn_list, bank_integration, bank_account): return {"success": True, "enqueued": True, "total": len(txn_list)} -def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user): - """Background job: create BT + upsert registry rows for each transaction.""" +def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user, load_registries=1): + """Background job: create BT (+ optionally upsert registry rows) for each transaction.""" frappe.set_user(user) bi = frappe.get_doc("Bank Integration", bank_integration) @@ -102,17 +109,18 @@ def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user): drcr = (txn.get("drcr") or "D").upper() purpose = (txn.get("purpose") or "").strip() - if counterparty: - if drcr == "C": - if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration): - new_customers += 1 - else: - if _upsert_supplier(counterparty, contr_voen, contr_iban, bank_integration): - new_suppliers += 1 + if load_registries: + if counterparty: + if drcr == "C": + if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration): + new_customers += 1 + else: + if _upsert_supplier(counterparty, contr_voen, contr_iban, bank_integration): + new_suppliers += 1 - if purpose: - if _upsert_purpose(purpose, drcr, bank_integration): - new_purposes += 1 + if purpose: + if _upsert_purpose(purpose, drcr, bank_integration): + new_purposes += 1 ref_no = (txn.get("ref_no") or "").strip() amount = abs(flt(txn.get("amount", 0))) @@ -258,3 +266,48 @@ def update_bank_account_bi_default(bank_account, bi_type, bi_name): }, update_modified=False) frappe.db.commit() return {"success": True} + + +@frappe.whitelist() +def load_registries_from_excel(file_url, preset_name, bank_integration, + load_customers=1, load_suppliers=1, load_purposes=1): + """Parse an Excel statement and upsert ONLY the selected reference registries + (Bank Integration Customer / Supplier / Purpose) — no Bank Transactions. + This is the "Load Data" action on the Bank Integration form, the file-based + counterpart to kapital_bank's API-driven counterparty/purpose loaders.""" + load_customers = cint(load_customers) + load_suppliers = cint(load_suppliers) + load_purposes = cint(load_purposes) + if not (load_customers or load_suppliers or load_purposes): + return {"success": False, "message": _("Select at least one type to load.")} + + transactions = parse_excel(file_url, preset_name) + + new_c = new_s = new_p = 0 + for txn in transactions: + counterparty = (txn.get("counterparty") or "").strip() + contr_voen = (txn.get("contr_voen") or "").strip() + contr_iban = (txn.get("contr_iban") or "").strip() + drcr = (txn.get("drcr") or "D").upper() + purpose = (txn.get("purpose") or "").strip() + + if counterparty: + if drcr == "C" and load_customers: + if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration): + new_c += 1 + elif drcr == "D" and load_suppliers: + if _upsert_supplier(counterparty, contr_voen, contr_iban, bank_integration): + new_s += 1 + + if purpose and load_purposes: + if _upsert_purpose(purpose, drcr, bank_integration): + new_p += 1 + + frappe.db.commit() + return { + "success": True, + "total_rows": len(transactions), + "new_customers": new_c, + "new_suppliers": new_s, + "new_purposes": new_p, + } diff --git a/jey_erp/jey_erp/doctype/bank_integration/bank_integration.js b/jey_erp/jey_erp/doctype/bank_integration/bank_integration.js index 73d64c2..ffa97a4 100644 --- a/jey_erp/jey_erp/doctype/bank_integration/bank_integration.js +++ b/jey_erp/jey_erp/doctype/bank_integration/bank_integration.js @@ -5,6 +5,11 @@ frappe.ui.form.on('Bank Integration', { refresh(frm) { if (frm.is_new()) return; + // === Load Data (file-based, registry-only) === + frm.add_custom_button(__('Load Data'), () => { + _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', @@ -133,3 +138,91 @@ function _call(frm, method, args, onSuccess) { }, }); } + +function _show_load_data_dialog(frm) { + const d = new frappe.ui.Dialog({ + title: __('Load Data from Excel'), + fields: [ + { + fieldname: 'preset', + fieldtype: 'Link', + options: 'Bank Integration Excel Preset', + label: __('Excel Preset'), + reqd: 1, + }, + { + fieldname: 'file_url', + fieldtype: 'Attach', + label: __('Excel File'), + reqd: 1, + }, + { fieldname: 'sec_what', fieldtype: 'Section Break', label: __('What to load') }, + { + fieldname: 'load_customers', + fieldtype: 'Check', + label: __('Customers (incoming counterparties)'), + default: 1, + }, + { + fieldname: 'load_suppliers', + fieldtype: 'Check', + label: __('Suppliers (outgoing counterparties)'), + default: 1, + }, + { + fieldname: 'load_purposes', + fieldtype: 'Check', + label: __('Purpose keywords'), + default: 1, + }, + ], + primary_action_label: __('Load'), + primary_action(values) { + if (!values.load_customers && !values.load_suppliers && !values.load_purposes) { + frappe.msgprint({ + title: __('Nothing Selected'), + indicator: 'orange', + message: __('Pick at least one type to load.'), + }); + return; + } + d.disable_primary_action(); + frappe.call({ + method: 'jey_erp.bank_integration.import_api.load_registries_from_excel', + args: { + file_url: values.file_url, + preset_name: values.preset, + bank_integration: frm.doc.name, + load_customers: values.load_customers ? 1 : 0, + load_suppliers: values.load_suppliers ? 1 : 0, + load_purposes: values.load_purposes ? 1 : 0, + }, + callback(r) { + d.enable_primary_action(); + if (!r.message || r.message.success === false) { + frappe.msgprint({ + title: __('Error'), + indicator: 'red', + message: (r.message && r.message.message) || __('Unknown error'), + }); + return; + } + d.hide(); + const res = r.message; + frappe.msgprint({ + title: __('Data Loaded'), + indicator: 'green', + message: __('From {0} rows — new customers: {1}, suppliers: {2}, purposes: {3}', + [res.total_rows, res.new_customers, res.new_suppliers, res.new_purposes]), + }); + frm.reload_doc(); + }, + error() { + d.enable_primary_action(); + frappe.msgprint({ title: __('Error'), indicator: 'red', message: __('Network error') }); + }, + }); + }, + }); + d.show(); +} diff --git a/jey_erp/public/js/bank_transaction_list.js b/jey_erp/public/js/bank_transaction_list.js index e484cef..99ed795 100644 --- a/jey_erp/public/js/bank_transaction_list.js +++ b/jey_erp/public/js/bank_transaction_list.js @@ -84,6 +84,13 @@ const BIExcelImport = { label: __('Excel File'), reqd: 1, }, + { + fieldname: 'load_registries', + fieldtype: 'Check', + label: __('Also load counterparties & purposes'), + default: 1, + description: __('Extract unique counterparties and purpose keywords from the file into the Bank Integration registries (status: New).'), + }, ], primary_action_label: __('Parse'), primary_action(values) { @@ -251,6 +258,7 @@ const BIExcelImport = { txn_list: JSON.stringify(selected), bank_integration: values.bank_integration, bank_account: values.bank_account, + load_registries: values.load_registries ? 1 : 0, }, error() { frappe.realtime.off('bi_bt_import_progress');