fix: don't assign group-type Customer/Supplier Group to auto-created parties

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) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-05-12 13:31:05 +00:00
parent 530289e94c
commit a90fe340d5
7 changed files with 66 additions and 19 deletions

View File

@ -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):

View File

@ -37,6 +37,7 @@
"fieldname": "customer_group",
"fieldtype": "Link",
"label": "Customer Group",
"link_filters": "[[\"Customer Group\",\"is_group\",\"=\",0]]",
"options": "Customer Group"
},
{

View File

@ -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"
},
{

View File

@ -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"
},
{

View File

@ -36,6 +36,7 @@
"fieldname": "supplier_group",
"fieldtype": "Link",
"label": "Supplier Group",
"link_filters": "[[\"Supplier Group\",\"is_group\",\"=\",0]]",
"options": "Supplier Group"
},
{

29
invoice_az/utils.py Normal file
View File

@ -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

View File

@ -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'
})