From a90fe340d590727039705c190a0bc8ea18b7ff47 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 12 May 2026 13:31:05 +0000 Subject: [PATCH] fix: don't assign group-type Customer/Supplier Group to auto-created parties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recent ERPNext rejects assigning a group-type Customer Group to a Customer ("Cannot select a Group type Customer Group"); customer_group is also no longer mandatory. The integration fell back to "All Customer Groups" (a group node), so every auto-created party failed. - add invoice_az/utils.py:resolve_customer_group() — returns the configured group only if it exists and is not a group node, else None - api.py / vat_api.py: skip customer_group when no valid non-group value; create the Customer without a group instead of forcing "All Customer Groups" - add link_filters (is_group=0) to all Customer/Supplier Group pickers in E-Taxes Settings and the mapping child tables Co-Authored-By: Claude Opus 4.7 (1M context) --- invoice_az/api.py | 33 ++++++++++++------- .../e_taxes_customer_mappings.json | 1 + .../e_taxes_party_mapping.json | 2 ++ .../e_taxes_settings/e_taxes_settings.json | 2 ++ .../e_taxes_supplier_mappings.json | 1 + invoice_az/utils.py | 29 ++++++++++++++++ invoice_az/vat_api.py | 17 ++++++---- 7 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 invoice_az/utils.py diff --git a/invoice_az/api.py b/invoice_az/api.py index c3af384..83a3972 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -11,6 +11,7 @@ import time # Импорты из модуля аутентификации from invoice_az.auth import record_etaxes_activity, get_default_asan_login +from .utils import resolve_customer_group from .sales_api import get_sales_invoices @@ -3153,12 +3154,16 @@ def create_unmapped_customers(): customer_doc.customer_name = etaxes_customer.etaxes_party_name customer_doc.customer_type = "Company" # Can be configured if needed - # Apply settings considering individual priorities - customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group) - if not frappe.db.exists('Customer Group', customer_group): - customer_group = "All Customer Groups" - customer_doc.customer_group = customer_group - + # Apply settings considering individual priorities. + # Only assign a Customer Group if a valid, non-group one is configured; + # otherwise leave it empty (ERPNext rejects group-type Customer Groups + # and the field is no longer mandatory). + customer_group = resolve_customer_group( + individual_settings.get('customer_group') or settings_doc.default_customer_group + ) + if customer_group: + customer_doc.customer_group = customer_group + # Territory territory = individual_settings.get('territory', "All Territories") if not frappe.db.exists('Territory', territory): @@ -3401,12 +3406,16 @@ def create_unmapped_customers_v2(): customer_doc.customer_name = customer_name customer_doc.customer_type = "Company" # Can be configured if needed - # Apply settings considering individual priorities - customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group) - if not frappe.db.exists('Customer Group', customer_group): - customer_group = "All Customer Groups" - customer_doc.customer_group = customer_group - + # Apply settings considering individual priorities. + # Only assign a Customer Group if a valid, non-group one is configured; + # otherwise leave it empty (ERPNext rejects group-type Customer Groups + # and the field is no longer mandatory). + customer_group = resolve_customer_group( + individual_settings.get('customer_group') or settings_doc.default_customer_group + ) + if customer_group: + customer_doc.customer_group = customer_group + # Territory territory = individual_settings.get('territory', "All Territories") if not frappe.db.exists('Territory', territory): diff --git a/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json index b45573a..d3d7958 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json +++ b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json @@ -37,6 +37,7 @@ "fieldname": "customer_group", "fieldtype": "Link", "label": "Customer Group", + "link_filters": "[[\"Customer Group\",\"is_group\",\"=\",0]]", "options": "Customer Group" }, { diff --git a/invoice_az/invoice_az/doctype/e_taxes_party_mapping/e_taxes_party_mapping.json b/invoice_az/invoice_az/doctype/e_taxes_party_mapping/e_taxes_party_mapping.json index 642c966..d894d71 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_party_mapping/e_taxes_party_mapping.json +++ b/invoice_az/invoice_az/doctype/e_taxes_party_mapping/e_taxes_party_mapping.json @@ -70,6 +70,7 @@ "fieldname": "supplier_group", "fieldtype": "Link", "label": "Supplier Group", + "link_filters": "[[\"Supplier Group\",\"is_group\",\"=\",0]]", "options": "Supplier Group" }, { @@ -78,6 +79,7 @@ "fieldname": "customer_group", "fieldtype": "Link", "label": "Customer Group", + "link_filters": "[[\"Customer Group\",\"is_group\",\"=\",0]]", "options": "Customer Group" }, { diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json index e9a4843..14fab83 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json +++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json @@ -103,12 +103,14 @@ "fieldname": "default_supplier_group", "fieldtype": "Link", "label": "Default Supplier Group", + "link_filters": "[[\"Supplier Group\",\"is_group\",\"=\",0]]", "options": "Supplier Group" }, { "fieldname": "default_customer_group", "fieldtype": "Link", "label": "Default Customer Group", + "link_filters": "[[\"Customer Group\",\"is_group\",\"=\",0]]", "options": "Customer Group" }, { diff --git a/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json index 082e281..c820727 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json +++ b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json @@ -36,6 +36,7 @@ "fieldname": "supplier_group", "fieldtype": "Link", "label": "Supplier Group", + "link_filters": "[[\"Supplier Group\",\"is_group\",\"=\",0]]", "options": "Supplier Group" }, { diff --git a/invoice_az/utils.py b/invoice_az/utils.py new file mode 100644 index 0000000..b8be92f --- /dev/null +++ b/invoice_az/utils.py @@ -0,0 +1,29 @@ +# ======= SHARED HELPERS ======= + +import frappe + + +def resolve_customer_group(preferred=None): + """Return a usable (non-group) Customer Group, or None. + + Newer ERPNext rejects assigning a Customer to a "group" type Customer Group + (e.g. the root "All Customer Groups"), and `customer_group` is no longer a + mandatory field. So: + - if a valid, non-group Customer Group is configured -> use it + - otherwise return None and let the Customer be created without a group + """ + if preferred and frappe.db.exists("Customer Group", preferred): + if not frappe.db.get_value("Customer Group", preferred, "is_group"): + return preferred + return None + + +def resolve_territory(preferred=None): + """Return a usable Territory, or None if the configured one is missing. + + Mirrors :func:`resolve_customer_group`; Territory root nodes are still + accepted by ERPNext, so we only validate existence. + """ + if preferred and frappe.db.exists("Territory", preferred): + return preferred + return None diff --git a/invoice_az/vat_api.py b/invoice_az/vat_api.py index 0796978..0f3f139 100644 --- a/invoice_az/vat_api.py +++ b/invoice_az/vat_api.py @@ -8,6 +8,7 @@ import json from datetime import datetime from frappe.utils import now_datetime, getdate from invoice_az.auth import record_etaxes_activity, get_default_asan_login +from invoice_az.utils import resolve_customer_group # API Endpoint VAT_OPERATIONS_URL = "https://new.e-taxes.gov.az/api/po/vatacc/public/v1/operation/find.outbox" @@ -349,17 +350,19 @@ def find_or_create_customer_from_vat_operation(tin, name, settings_doc, auto_cre return customer['name'] # Шаг 4: Создание нового Customer - # Получаем default customer group - default_customer_group = settings_doc.default_customer_group if settings_doc else None - if not default_customer_group or not frappe.db.exists('Customer Group', default_customer_group): - default_customer_group = 'All Customer Groups' + # Берём настроенную группу только если она валидна и не является group-узлом; + # иначе создаём Customer без группы (ERPNext запрещает group-type Customer Group). + default_customer_group = resolve_customer_group( + settings_doc.default_customer_group if settings_doc else None + ) # Создаем Customer (с защитой от race condition) try: customer_doc = frappe.new_doc('Customer') customer_doc.customer_name = name or f"Customer-{tin}" customer_doc.customer_type = 'Company' - customer_doc.customer_group = default_customer_group + if default_customer_group: + customer_doc.customer_group = default_customer_group customer_doc.territory = 'All Territories' customer_doc.tax_id = tin @@ -410,7 +413,7 @@ def _create_or_update_etaxes_customer_and_mapping(tin, etaxes_name, erp_customer if settings_doc: if existing_mapping_row: # Используем прямое обновление child table через SQL - default_customer_group = settings_doc.default_customer_group if settings_doc.default_customer_group else 'All Customer Groups' + default_customer_group = resolve_customer_group(settings_doc.default_customer_group) # Обновляем через frappe.db.sql напрямую frappe.db.sql(""" @@ -438,7 +441,7 @@ def _create_or_update_etaxes_customer_and_mapping(tin, etaxes_name, erp_customer 'etaxes_customer_name': etaxes_name or f"Customer-{tin}", 'etaxes_tax_id': tin, 'erp_customer': erp_customer, - 'customer_group': settings_doc.default_customer_group if settings_doc.default_customer_group else 'All Customer Groups', + 'customer_group': resolve_customer_group(settings_doc.default_customer_group), 'territory': 'All Territories', 'mapping_type': 'Automatic' })