From 853c01e3e9d68607c51417da8116564f447e646d Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 12 May 2026 10:53:29 +0000 Subject: [PATCH] feat: fall back to first leaf node when no group is configured + filter group fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating customers/suppliers no longer requires a default Customer/ Supplier Group or Territory on Kapital Bank Settings — when none is set, the first leaf (non-group) node of the tree is used. A configured value is still validated (must exist and be a leaf), with a clear message. Also: the default-group / default-territory Link fields (and the matching mapping child-table fields) now filter to is_group=0 so a group node can't be selected. Co-Authored-By: Claude Opus 4.7 (1M context) --- kapital_bank/bank_api.py | 62 ++++++++++++------- .../kapital_bank_settings.js | 11 ++++ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/kapital_bank/bank_api.py b/kapital_bank/bank_api.py index 0a56b27..013a814 100644 --- a/kapital_bank/bank_api.py +++ b/kapital_bank/bank_api.py @@ -25,6 +25,25 @@ def _api_error_message(resp): return f"HTTP {resp.status_code}: {resp.text[:200]}" +def _resolve_leaf_node(doctype, configured, label): + """Resolve `configured` to a valid leaf node of an NSM tree doctype + (Customer Group / Supplier Group / Territory). Empty config falls back to + the first leaf node so creation works without a default set. + + Returns (value, error_message). On success error_message is None. + """ + if configured: + if not frappe.db.exists(doctype, configured): + return None, f"{label} '{configured}' does not exist." + if frappe.db.get_value(doctype, configured, "is_group"): + return None, f"{label} '{configured}' is a group node — pick a leaf (non-group) {label}." + return configured, None + leaf = frappe.db.get_value(doctype, {"is_group": 0}, "name", order_by="lft asc") + if not leaf: + return None, f"No non-group {label} exists. Create one first." + return leaf, None + + # ═══════════════════════════════════════════════════════════════════════════════ # REGISTRY LOADERS (fetch from Kapital Bank → save to local DocTypes) # ═══════════════════════════════════════════════════════════════════════════════ @@ -658,22 +677,21 @@ def create_unmapped_customers(settings_name=None): if not unmapped: return {"success": True, "created_count": 0, "message": "No unmapped customers in table to create"} - # Validate config upfront — clearer than ERPNext's errors thrown deep inside - # party creation ("Could not find Customer Group" / "Cannot select a Group type"). + # Resolve group / territory per row upfront — empty config falls back to + # the first leaf node, so creation works without a default set, and bad + # config surfaces a clear message instead of an ERPNext error mid-create. + resolved = [] for mapping, _kb_customer in unmapped: - group = mapping.customer_group or doc.default_customer_group - if not group or not frappe.db.exists("Customer Group", group): - return {"success": False, "message": "Set a Default Customer Group on Kapital Bank Settings (or a Customer Group on each customer mapping row) before creating customers."} - if frappe.db.get_value("Customer Group", group, "is_group"): - return {"success": False, "message": f"Customer Group '{group}' is a group node — pick a leaf (non-group) Customer Group."} - territory = mapping.territory or doc.default_territory - if not territory or not frappe.db.exists("Territory", territory): - return {"success": False, "message": "Set a Default Territory on Kapital Bank Settings (or a Territory on each customer mapping row) before creating customers."} - if frappe.db.get_value("Territory", territory, "is_group"): - return {"success": False, "message": f"Territory '{territory}' is a group node — pick a leaf (non-group) Territory."} + cg, err = _resolve_leaf_node("Customer Group", mapping.customer_group or doc.default_customer_group, "Customer Group") + if err: + return {"success": False, "message": err} + terr, err = _resolve_leaf_node("Territory", mapping.territory or doc.default_territory, "Territory") + if err: + return {"success": False, "message": err} + resolved.append((cg, terr)) created = 0 - for mapping, kb_customer in unmapped: + for (mapping, kb_customer), (customer_group, territory) in zip(unmapped, resolved): try: customer_name = kb_customer.customer_name if frappe.db.exists("Customer", {"customer_name": customer_name}): @@ -682,8 +700,8 @@ def create_unmapped_customers(settings_name=None): party = frappe.new_doc("Customer") party.customer_name = customer_name party.customer_type = "Company" - party.customer_group = mapping.customer_group or doc.default_customer_group - party.territory = mapping.territory or doc.default_territory + party.customer_group = customer_group + party.territory = territory if kb_customer.tax_id: party.tax_id = kb_customer.tax_id @@ -739,15 +757,15 @@ def create_unmapped_suppliers(settings_name=None): if not unmapped: return {"success": True, "created_count": 0, "message": "No unmapped suppliers in table to create"} + resolved = [] for mapping, _kb_supplier in unmapped: - group = mapping.supplier_group or doc.default_supplier_group - if not group or not frappe.db.exists("Supplier Group", group): - return {"success": False, "message": "Set a Default Supplier Group on Kapital Bank Settings (or a Supplier Group on each supplier mapping row) before creating suppliers."} - if frappe.db.get_value("Supplier Group", group, "is_group"): - return {"success": False, "message": f"Supplier Group '{group}' is a group node — pick a leaf (non-group) Supplier Group."} + sg, err = _resolve_leaf_node("Supplier Group", mapping.supplier_group or doc.default_supplier_group, "Supplier Group") + if err: + return {"success": False, "message": err} + resolved.append(sg) created = 0 - for mapping, kb_supplier in unmapped: + for (mapping, kb_supplier), supplier_group in zip(unmapped, resolved): try: supplier_name = kb_supplier.supplier_name if frappe.db.exists("Supplier", {"supplier_name": supplier_name}): @@ -756,7 +774,7 @@ def create_unmapped_suppliers(settings_name=None): party = frappe.new_doc("Supplier") party.supplier_name = supplier_name party.supplier_type = "Company" - party.supplier_group = mapping.supplier_group or doc.default_supplier_group + party.supplier_group = supplier_group if kb_supplier.tax_id: party.tax_id = kb_supplier.tax_id 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 3846195..3d728c6 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 @@ -1,4 +1,15 @@ frappe.ui.form.on('Kapital Bank Settings', { + setup(frm) { + // Customer/Supplier Group and Territory must be leaf (non-group) nodes. + const leaf = () => ({ filters: { is_group: 0 } }); + frm.set_query('default_customer_group', leaf); + frm.set_query('default_supplier_group', leaf); + frm.set_query('default_territory', leaf); + frm.set_query('customer_group', 'customer_mappings', leaf); + frm.set_query('territory', 'customer_mappings', leaf); + frm.set_query('supplier_group', 'supplier_mappings', leaf); + }, + refresh(frm) { // ── Main: Load Data from Kapital Bank ──────────────────────────────── frm.add_custom_button(__('Load Data'), () => {