diff --git a/kapital_bank/client/bank_reconciliation_tool.js b/kapital_bank/client/bank_reconciliation_tool.js
deleted file mode 100644
index ef8f79e..0000000
--- a/kapital_bank/client/bank_reconciliation_tool.js
+++ /dev/null
@@ -1,348 +0,0 @@
-// Kapital Bank — Bank Reconciliation Tool integration
-// Adds checkbox column + "Create & Reconcile" toolbar
-
-frappe.ui.form.on("Bank Reconciliation Tool", {
- render: function (frm) {
- // Only activate if at least one Kapital Bank Login exists
- frappe.db.count("Kapital Bank Login").then(function (count) {
- if (!count) return;
-
- if (frm._kb_check_interval) clearInterval(frm._kb_check_interval);
-
- frm._kb_check_interval = setInterval(function () {
- const dtm = frm.bank_reconciliation_data_table_manager;
- if (dtm && dtm.datatable && !dtm._kb_enhanced) {
- clearInterval(frm._kb_check_interval);
- KBBRTMapping.enhance(frm, dtm);
- }
- }, 200);
-
- setTimeout(function () { clearInterval(frm._kb_check_interval); }, 15000);
- });
- },
-});
-
-const KBBRTMapping = {
- enhance: function (frm, dtm) {
- dtm._kb_enhanced = true;
- dtm._kb_frm = frm;
-
- // Rebuild DataTable with checkboxColumn: true
- dtm.datatable.destroy();
- dtm.datatable = new frappe.DataTable(dtm.$reconciliation_tool_dt.get(0), {
- columns: dtm.columns,
- data: dtm.transactions,
- dynamicRowHeight: true,
- checkboxColumn: true,
- inlineFilters: true,
- });
- $(`.${dtm.datatable.style.scopeClass} .dt-scrollable`).css("max-height", "calc(100vh - 400px)");
-
- // Restore ERPNext's Action-button listeners for the new datatable
- dtm.set_listeners();
-
- dtm._kb_scopeClass = dtm.datatable.style.scopeClass;
-
- KBBRTMapping.injectToolbar(dtm);
- },
-
- injectToolbar: function (dtm) {
- $("#kb-brt-mapping-toolbar").remove();
-
- const $toolbar = $(`
-
").html(row[2] || "").text().trim();
- const deposit = parseFloat(row[4]) || 0;
- const withdrawal = parseFloat(row[5]) || 0;
- // row[8] = Actions button (original) OR plain name inserted by jey_erp BRT patch
- // row[9] = Actions button when jey_erp patch is active (shifted by 1)
- const bankTransactionName = $(row[9] || row[8] || "").data("name") || "";
- txns.push({
- purpose: description,
- drcr: withdrawal > 0 ? "D" : "C",
- party_type: partyType,
- party: partyName,
- amount: deposit || withdrawal,
- date: (row[0] || "").trim(),
- reference_number: (row[7] || "").trim(),
- bank_transaction_name: bankTransactionName,
- });
- });
-
- // Deduplicate for preview only
- const purposeMap = new Map();
- txns.forEach(function (txn) {
- const key = txn.purpose + "|" + txn.drcr;
- if (purposeMap.has(key)) {
- purposeMap.get(key).count++;
- } else {
- purposeMap.set(key, {
- purpose: txn.purpose,
- payment_type: txn.drcr === "D" ? "Pay" : "Receive",
- party: txn.party,
- count: 1,
- });
- }
- });
-
- if (purposeMap.size === 0) {
- frappe.msgprint({ title: __("No Purposes"), indicator: "orange",
- message: __("All selected rows have no description text.") });
- return;
- }
-
- // Preview table
- let previewHtml = '
' +
- '
' +
- "" +
- "| " + __("Description") + " | " +
- '' + __("Type") + " | " +
- '' + __("Party") + " | " +
- '' + __("Rows") + " | " +
- "
";
- purposeMap.forEach(function (e) {
- const badge = e.payment_type === "Pay"
- ? 'Pay'
- : 'Receive';
- previewHtml += "" +
- '| ' + frappe.utils.escape_html(e.purpose) + " | " +
- "" + badge + " | " +
- "" + (e.party ? frappe.utils.escape_html(e.party) : '—') + " | " +
- '' + e.count + " |
";
- });
- previewHtml += "
";
- if (skippedCount > 0) {
- previewHtml += '
' +
- __("{0} row(s) skipped — no description text.", [skippedCount]) + "
";
- }
-
- const d = new frappe.ui.Dialog({
- title: __("Create & Reconcile") + " (" + purposeMap.size + ")",
- size: "large",
- fields: [
- {
- fieldname: "mode",
- fieldtype: "Select",
- label: __("Action"),
- options: "Both\nMappings Only\nDocuments & Reconcile",
- default: "Both",
- reqd: 1,
- onchange: function () { KBBRTMapping._applyModeVisibility(d); },
- },
- { fieldname: "preview_html", fieldtype: "HTML", options: previewHtml },
- { fieldname: "sec_options", fieldtype: "Section Break", label: __("Options") },
- {
- fieldname: "document_type",
- fieldtype: "Select",
- label: __("Document Type"),
- options: "Payment Entry\nJournal Entry",
- default: "Payment Entry",
- },
- {
- fieldname: "use_original_purpose",
- fieldtype: "Check",
- label: __("Use original description"),
- onchange: function () {
- const on = d.get_value("use_original_purpose");
- d.set_df_property("custom_purpose", "hidden", on ? 1 : 0);
- d.get_field("custom_purpose").refresh();
- },
- },
- {
- fieldname: "custom_purpose",
- fieldtype: "Data",
- label: __("Custom Purpose Keyword"),
- hidden: 0,
- description: __("Leave empty to map by party only. All rows will use this keyword instead of their own description."),
- },
- { fieldname: "sec_accounts", fieldtype: "Section Break", label: __("GL Accounts") },
- {
- fieldname: "paid_from", fieldtype: "Link", options: "Account",
- label: __("Paid From (Account)"),
- },
- { fieldname: "col_break", fieldtype: "Column Break" },
- {
- fieldname: "paid_to", fieldtype: "Link", options: "Account",
- label: __("Paid To (Account)"),
- },
- ],
- primary_action_label: __("Create & Reconcile"),
- primary_action: function () {
- const isDR = d.get_value("mode") === "Documents & Reconcile";
- const values = d.get_values(true);
- if (!values) return;
- if (!isDR) {
- const missing = [];
- if (!values.paid_from) missing.push(__("Paid From (Account)"));
- if (!values.paid_to) missing.push(__("Paid To (Account)"));
- if (missing.length) {
- frappe.msgprint({ title: __("Missing Values"), indicator: "red",
- message: __("Following fields are required:") + "
" + missing.join("
") });
- return;
- }
- }
-
- let finalTxns = txns.slice();
- if (values.mode !== "Documents & Reconcile") {
- if (!values.use_original_purpose) {
- const cp = (values.custom_purpose || "").trim();
- if (cp) {
- finalTxns = txns.map(t => Object.assign({}, t, { purpose: cp }));
- } else {
- finalTxns = txns.map(t => Object.assign({}, t, { purpose: "" }));
- if (finalTxns.every(t => !t.party)) {
- frappe.msgprint({
- title: __("Validation Error"),
- indicator: "red",
- message: __("Either a purpose keyword or a party is required."),
- });
- return;
- }
- }
- }
- }
-
- KBBRTMapping.submitMappings(d, finalTxns, values.paid_from, values.paid_to, values.document_type, values.mode);
- },
- });
-
- d._kb_frm = dtm._kb_frm;
- d.show();
- KBBRTMapping._applyModeVisibility(d);
-
- // Pre-fill custom purpose from the Description inline filter if active
- const $fi = $(`.${dtm._kb_scopeClass} .dt-row-filter input.dt-filter[data-name="Description"]`);
- const filterVal = ($fi.val() || "").trim();
- if (filterVal) {
- d.set_value("custom_purpose", filterVal);
- }
- },
-
- _applyModeVisibility: function (d) {
- const isDR = d.get_value("mode") === "Documents & Reconcile";
- const toHide = ["preview_html", "sec_options", "document_type", "use_original_purpose",
- "custom_purpose", "sec_accounts", "paid_from", "col_break", "paid_to"];
- toHide.forEach(fn => d.set_df_property(fn, "hidden", isDR ? 1 : 0));
- d.refresh_fields(toHide);
- },
-
- submitMappings: function (dialog, txns, paid_from, paid_to, document_type, mode) {
- dialog.disable_primary_action();
- frappe.call({
- method: "kapital_bank.mapping.create_purpose_mappings",
- args: {
- transactions: JSON.stringify(txns),
- paid_from: paid_from,
- paid_to: paid_to,
- document_type: document_type || "Payment Entry",
- mode: mode || "Both",
- },
- callback: function (r) {
- dialog.enable_primary_action();
- if (!r.message || !r.message.success) {
- frappe.msgprint({ title: __("Error"), indicator: "red",
- message: r.message ? r.message.message : __("Unknown error") });
- return;
- }
- dialog.hide();
- const res = r.message;
- const parts = [];
- if (res.created_mappings) parts.push(__("Mappings created: {0}", [res.created_mappings]));
- if (res.already_mapped) parts.push(__("Already mapped: {0}", [res.already_mapped]));
- if (res.skipped_no_data) parts.push(__("Skipped (no purpose): {0}", [res.skipped_no_data]));
- if (res.created_docs) parts.push(__("Documents created: {0}", [res.created_docs]));
- if (res.reconciled) parts.push(__("Reconciled: {0}", [res.reconciled]));
- if (res.errors && res.errors.length) parts.push(__("Errors: {0}", [res.errors.length]));
- frappe.show_alert({
- message: parts.join(" • ") || __("No changes"),
- indicator: (res.created_mappings > 0 || res.created_docs > 0) ? "green" : "blue",
- }, 5);
- if (dialog._kb_frm) dialog._kb_frm.refresh();
- if (res.errors && res.errors.length) {
- KBBRTMapping.showErrors(res.errors);
- }
- },
- error: function () {
- dialog.enable_primary_action();
- frappe.msgprint({ title: __("Error"), indicator: "red",
- message: __("Network error creating mappings") });
- },
- });
- },
-
- showErrors: function (errors) {
- let html = '
';
- errors.forEach(function (err) {
- const ref = err.reference_number || __("Unknown");
- html += '
';
- html += '
';
- html += '' + frappe.utils.escape_html(ref) + '';
- html += '' + __("Error") + '';
- html += '
';
- html += '
' + frappe.utils.escape_html(err.message || __("Unknown error")) + '
';
- html += '
';
- });
- html += '
';
-
- const d = new frappe.ui.Dialog({
- title: __("Errors ({0})", [errors.length]),
- size: "large",
- fields: [{ fieldname: "errors_html", fieldtype: "HTML", options: html }],
- primary_action_label: __("Close"),
- primary_action: function () { d.hide(); },
- });
- d.show();
- },
-};
diff --git a/kapital_bank/client/bank_transaction.js b/kapital_bank/client/bank_transaction.js
index d6ecf41..f518c9e 100644
--- a/kapital_bank/client/bank_transaction.js
+++ b/kapital_bank/client/bank_transaction.js
@@ -722,10 +722,19 @@ KBBTImport.import = {
};
// ======= LIST VIEW SETUP =======
-frappe.listview_settings['Bank Transaction'] = {
- onload(listview) {
- listview.page.add_inner_button(__('Import'), function() {
- KBBTImport.import.showDialog();
- }, __('Kapital Bank'));
- }
-};
+// Coexists with jey_erp's bank_transaction_list.js which creates the
+// shared "Import From..." button group; we add our item into the same group.
+(function () {
+ const existing = frappe.listview_settings['Bank Transaction'] || {};
+ const prevOnload = existing.onload;
+ frappe.listview_settings['Bank Transaction'] = Object.assign({}, existing, {
+ onload(listview) {
+ if (typeof prevOnload === 'function') {
+ try { prevOnload(listview); } catch (e) { console.error(e); }
+ }
+ listview.page.add_inner_button(__('Load from Kapital Bank'), function () {
+ KBBTImport.import.showDialog();
+ }, __('Import From...'));
+ }
+ });
+})();
diff --git a/kapital_bank/hooks.py b/kapital_bank/hooks.py
index 251754e..0302511 100644
--- a/kapital_bank/hooks.py
+++ b/kapital_bank/hooks.py
@@ -11,10 +11,13 @@ after_migrate = "kapital_bank.setup.after_migrate"
# JS for ERPNext forms
+# Note: Bank Reconciliation Tool extension (Create & Reconcile, checkbox column,
+# mapping-source select) lives in jey_erp/public/js/bank_reconciliation_tool.js
+# and works with both Kapital Bank Settings and Bank Integration mappings via
+# the universal resolver in jey_erp/bank_integration/mapping_resolver.py.
doctype_js = {
"Payment Entry": "client/payment_entry.js",
"Bank Transaction": "client/bank_transaction.js",
- "Bank Reconciliation Tool": "client/bank_reconciliation_tool.js",
"Payment Request": "client/payment_request.js",
}