diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js index 0876299..080bc63 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.js @@ -146,6 +146,17 @@ frappe.ui.form.on('Kapital Bank Settings', { } }, __('Purpose')); + // ── Bulk Edit buttons for mapping tables ───────────────────────────── + const mapping_tables = [ + 'account_mappings', 'card_mappings', 'customer_mappings', + 'supplier_mappings', 'transaction_mappings' + ]; + mapping_tables.forEach(table => { + frm.fields_dict[table]?.grid.add_custom_button(__('Bulk Edit'), () => { + bulk_edit_table(frm, table); + }); + }); + // ── Populate data tabs ──────────────────────────────────────────────── load_data_tabs(frm); } @@ -1029,6 +1040,198 @@ function render_purposes_list(purposes, total_count) { }); } +// ═══════════════════════════════════════════════════════════════════════════════ +// BULK EDIT +// ═══════════════════════════════════════════════════════════════════════════════ + +function bulk_edit_table(frm, table_field) { + const selected_rows = frm.fields_dict[table_field].grid.get_selected_children(); + + if (selected_rows.length === 0) { + frappe.msgprint(__("No rows selected. Please select rows to edit.")); + return; + } + + const child_doctype = selected_rows[0].doctype; + const meta = frappe.get_meta(child_doctype); + + const field_options = []; + const field_map = {}; + + meta.fields.forEach(df => { + if (!['Section Break', 'Column Break', 'Tab Break', 'HTML', 'Table', 'Button'].includes(df.fieldtype) + && !df.read_only && !df.hidden) { + field_options.push({ + label: __(df.label || frappe.model.unscrub(df.fieldname)), + value: df.fieldname + }); + field_map[df.fieldname] = df; + } + }); + + const d = new frappe.ui.Dialog({ + title: __("Bulk Edit Selected Rows"), + fields: [ + { + label: __("Field to Edit"), + fieldname: "field_to_edit", + fieldtype: "Select", + options: field_options, + reqd: 1 + }, + { + fieldname: "value_section", + fieldtype: "Section Break", + label: __("New Value"), + collapsible: 0, + hidden: 1 + }, + { + fieldname: "value_container", + fieldtype: "HTML", + options: '
' + } + ], + primary_action_label: __("Apply"), + primary_action() { + const field_name = d.get_value("field_to_edit"); + const field_def = field_map[field_name]; + + if (!field_name || !d.new_value_field) { + frappe.msgprint(__("Please select a field and enter a value.")); + return; + } + + const new_value = d.new_value_field.get_value(); + + if (new_value === undefined || new_value === null || new_value === "") { + frappe.msgprint(__("Please enter a new value.")); + return; + } + + // For Dynamic Link, also set the type field + const is_dynamic = field_def && field_def.fieldtype === "Dynamic Link"; + const type_field = is_dynamic ? field_def.options : null; + const type_value = is_dynamic && d.dynamic_link_type_field + ? d.dynamic_link_type_field.get_value() : null; + + if (is_dynamic && !type_value) { + frappe.msgprint(__("Please select a type.")); + return; + } + + let count = 0; + const promises = []; + + selected_rows.forEach(row => { + if (is_dynamic && type_field) { + promises.push( + frappe.model.set_value(row.doctype, row.name, type_field, type_value) + ); + } + promises.push( + frappe.model.set_value(row.doctype, row.name, field_name, new_value) + .then(() => { count++; }) + ); + }); + + Promise.all(promises).then(() => { + frm.refresh_field(table_field); + frappe.show_alert({ + message: __("Updated {0} rows", [count]), + indicator: 'green' + }, 5); + d.hide(); + }); + } + }); + + function clear_value_controls() { + if (d.new_value_field) { + d.new_value_field.$wrapper.remove(); + delete d.new_value_field; + } + if (d.dynamic_link_type_field) { + d.dynamic_link_type_field.$wrapper.remove(); + delete d.dynamic_link_type_field; + } + } + + function make_value_control(field_def, container) { + d.new_value_field = frappe.ui.form.make_control({ + df: { + label: __("New Value for {0}", [__(field_def.label || frappe.model.unscrub(field_def.fieldname))]), + fieldname: "new_value", + fieldtype: field_def.fieldtype === "Dynamic Link" ? "Link" : field_def.fieldtype, + options: field_def.options || "", + reqd: 1 + }, + parent: container, + render_input: true + }); + d.new_value_field.refresh(); + } + + d.fields_dict.field_to_edit.$input.on('change', function() { + const field_name = d.get_value("field_to_edit"); + const field_def = field_map[field_name]; + + if (!field_def) return; + + d.set_df_property("value_section", "hidden", 0); + clear_value_controls(); + + const value_container = d.fields_dict.value_container.$wrapper.find("#value_field_container"); + + if (field_def.fieldtype === "Dynamic Link") { + // Find the linked Select field (e.g. counterparty_type for counterparty) + const type_df = field_map[field_def.options]; + if (type_df) { + d.dynamic_link_type_field = frappe.ui.form.make_control({ + df: { + label: __(type_df.label || frappe.model.unscrub(type_df.fieldname)), + fieldname: "dynamic_link_type", + fieldtype: type_df.fieldtype, + options: type_df.options || "", + reqd: 1 + }, + parent: value_container, + render_input: true + }); + d.dynamic_link_type_field.refresh(); + + d.dynamic_link_type_field.$input.on('change', () => { + const selected_type = d.dynamic_link_type_field.get_value(); + if (d.new_value_field) { + d.new_value_field.$wrapper.remove(); + delete d.new_value_field; + } + if (selected_type) { + d.new_value_field = frappe.ui.form.make_control({ + df: { + label: __("New Value for {0}", [__(field_def.label || frappe.model.unscrub(field_def.fieldname))]), + fieldname: "new_value", + fieldtype: "Link", + options: selected_type, + reqd: 1 + }, + parent: value_container, + render_input: true + }); + d.new_value_field.refresh(); + } + }); + } else { + make_value_control(field_def, value_container); + } + } else { + make_value_control(field_def, value_container); + } + }); + + d.show(); +} + // ═══════════════════════════════════════════════════════════════════════════════ // HELPERS // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json index a109c4d..944ddce 100644 --- a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/kapital_bank_settings.json @@ -1,6 +1,6 @@ { "actions": [], - "creation": "2026-02-24 00:00:00.000000", + "creation": "2026-02-24 00:00:00", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -219,8 +219,7 @@ }, { "fieldname": "data_tab_html", - "fieldtype": "HTML", - "options": "" + "fieldtype": "HTML" }, { "fieldname": "mappings_tab", @@ -229,8 +228,7 @@ }, { "fieldname": "mappings_tab_html", - "fieldtype": "HTML", - "options": "" + "fieldtype": "HTML" }, { "fieldname": "account_mappings_tab", @@ -244,6 +242,7 @@ "label": "Account Mappings" }, { + "allow_bulk_edit": 1, "fieldname": "account_mappings", "fieldtype": "Table", "label": "Account Mappings", @@ -261,6 +260,7 @@ "label": "Card Mappings" }, { + "allow_bulk_edit": 1, "fieldname": "card_mappings", "fieldtype": "Table", "label": "Card Mappings", @@ -278,6 +278,7 @@ "label": "Customer Mappings" }, { + "allow_bulk_edit": 1, "fieldname": "customer_mappings", "fieldtype": "Table", "label": "Customer Mappings", @@ -295,6 +296,7 @@ "label": "Supplier Mappings" }, { + "allow_bulk_edit": 1, "fieldname": "supplier_mappings", "fieldtype": "Table", "label": "Supplier Mappings", @@ -321,7 +323,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-02-27 00:00:00.000000", + "modified": "2026-03-05 22:08:29.828228", "modified_by": "Administrator", "module": "Kapital Bank", "name": "Kapital Bank Settings", @@ -331,10 +333,8 @@ "create": 1, "delete": 1, "email": 1, - "export": 1, "print": 1, "read": 1, - "report": 1, "role": "System Manager", "share": 1, "write": 1 diff --git a/kapital_bank/kapital_bank/doctype/kapital_bank_settings/test_kapital_bank_settings.py b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/test_kapital_bank_settings.py new file mode 100644 index 0000000..ae8b165 --- /dev/null +++ b/kapital_bank/kapital_bank/doctype/kapital_bank_settings/test_kapital_bank_settings.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Jey ERP and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestKapitalBankSettings(IntegrationTestCase): + """ + Integration tests for KapitalBankSettings. + Use this class for testing interactions between multiple components. + """ + + pass