feat(bank-integration): optional registry loading + "Load Data" button
The Excel import dialog on the Bank Transaction list gains an "Also load counterparties & purposes" checkbox (default on — preserves current behaviour). When unchecked, only Bank Transactions are created and the Bank Integration Customer/Supplier/Purpose registries are left alone. The Bank Integration form gains a "Load Data" button: pick an Excel preset + file and choose which registries to populate (Customers / Suppliers / Purposes checkboxes, like the standard data import). It runs the same upsert logic but creates no Bank Transactions — the file-based counterpart to kapital_bank's API-driven counterparty/purpose loaders. Backed by jey_erp.bank_integration.import_api.load_registries_from_excel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e600a77806
commit
1d6bdc8424
|
|
@ -4,7 +4,7 @@ import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
from jey_erp.bank_integration.excel_parser import parse_excel
|
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()
|
@frappe.whitelist()
|
||||||
def import_bulk_bt(txn_list, bank_integration, bank_account):
|
def import_bulk_bt(txn_list, bank_integration, bank_account, load_registries=1):
|
||||||
"""Enqueue bulk Bank Transaction creation as a background job."""
|
"""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):
|
if isinstance(txn_list, str):
|
||||||
txn_list = json.loads(txn_list)
|
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_integration=bank_integration,
|
||||||
bank_account=bank_account,
|
bank_account=bank_account,
|
||||||
user=user,
|
user=user,
|
||||||
|
load_registries=cint(load_registries),
|
||||||
queue="default",
|
queue="default",
|
||||||
timeout=600,
|
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)}
|
return {"success": True, "enqueued": True, "total": len(txn_list)}
|
||||||
|
|
||||||
|
|
||||||
def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user):
|
def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user, load_registries=1):
|
||||||
"""Background job: create BT + upsert registry rows for each transaction."""
|
"""Background job: create BT (+ optionally upsert registry rows) for each transaction."""
|
||||||
frappe.set_user(user)
|
frappe.set_user(user)
|
||||||
|
|
||||||
bi = frappe.get_doc("Bank Integration", bank_integration)
|
bi = frappe.get_doc("Bank Integration", bank_integration)
|
||||||
|
|
@ -102,6 +109,7 @@ def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user):
|
||||||
drcr = (txn.get("drcr") or "D").upper()
|
drcr = (txn.get("drcr") or "D").upper()
|
||||||
purpose = (txn.get("purpose") or "").strip()
|
purpose = (txn.get("purpose") or "").strip()
|
||||||
|
|
||||||
|
if load_registries:
|
||||||
if counterparty:
|
if counterparty:
|
||||||
if drcr == "C":
|
if drcr == "C":
|
||||||
if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration):
|
if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration):
|
||||||
|
|
@ -258,3 +266,48 @@ def update_bank_account_bi_default(bank_account, bi_type, bi_name):
|
||||||
}, update_modified=False)
|
}, update_modified=False)
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
return {"success": True}
|
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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ frappe.ui.form.on('Bank Integration', {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
if (frm.is_new()) return;
|
if (frm.is_new()) return;
|
||||||
|
|
||||||
|
// === Load Data (file-based, registry-only) ===
|
||||||
|
frm.add_custom_button(__('Load Data'), () => {
|
||||||
|
_show_load_data_dialog(frm);
|
||||||
|
});
|
||||||
|
|
||||||
// === Accounts group ===
|
// === Accounts group ===
|
||||||
frm.add_custom_button(__('Match accounts to Bank Accounts'), () => {
|
frm.add_custom_button(__('Match accounts to Bank Accounts'), () => {
|
||||||
_safe(frm, () => _call(frm, 'jey_erp.bank_integration.matching.match_similar_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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,13 @@ const BIExcelImport = {
|
||||||
label: __('Excel File'),
|
label: __('Excel File'),
|
||||||
reqd: 1,
|
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_label: __('Parse'),
|
||||||
primary_action(values) {
|
primary_action(values) {
|
||||||
|
|
@ -251,6 +258,7 @@ const BIExcelImport = {
|
||||||
txn_list: JSON.stringify(selected),
|
txn_list: JSON.stringify(selected),
|
||||||
bank_integration: values.bank_integration,
|
bank_integration: values.bank_integration,
|
||||||
bank_account: values.bank_account,
|
bank_account: values.bank_account,
|
||||||
|
load_registries: values.load_registries ? 1 : 0,
|
||||||
},
|
},
|
||||||
error() {
|
error() {
|
||||||
frappe.realtime.off('bi_bt_import_progress');
|
frappe.realtime.off('bi_bt_import_progress');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue