From d3775786a3a16ac4463858e53078afdd2b830a8e Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Fri, 27 Mar 2026 19:40:46 +0400 Subject: [PATCH] fix: respect data loading checkboxes in reference data loading Checkbox values (load_items, load_units, load_customers, load_suppliers) were collected in the dialog but never passed through the loading pipeline, causing all data types to always be created regardless of user selection. Co-Authored-By: Claude Opus 4.6 (1M context) --- invoice_az/api.py | 336 +++++++++--------- .../e_taxes_settings/e_taxes_settings.js | 37 +- 2 files changed, 198 insertions(+), 175 deletions(-) diff --git a/invoice_az/api.py b/invoice_az/api.py index b73cf79..051b6a7 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -3764,14 +3764,16 @@ def get_reference_data_lists(data_type, limit=100, offset=0): return {'success': False, 'message': str(e)} @frappe.whitelist() -def process_single_invoice_for_reference_data(token, invoice_id, source_type='purchase'): +def process_single_invoice_for_reference_data(token, invoice_id, source_type='purchase', + load_items=1, load_units=1, + load_customers=1, load_suppliers=1): """Обрабатывает один инвойс и создает все 4 типа доктайпов""" record_etaxes_activity() - + try: # Получаем детали инвойса invoice_details = get_invoice_details(token, invoice_id) - + # Проверяем на ошибку unauthorized и пробуем обновить токен if isinstance(invoice_details, dict) and invoice_details.get('error') == 'unauthorized': # Пробуем получить новый токен из Asan Login @@ -3780,7 +3782,7 @@ def process_single_invoice_for_reference_data(token, invoice_id, source_type='pu new_token = token_result.get('token') # Повторяем запрос с новым токеном invoice_details = get_invoice_details(new_token, invoice_id) - + # Если все еще ошибка - возвращаем с обновленным токеном для клиента if isinstance(invoice_details, dict) and 'error' in invoice_details: return { @@ -3805,8 +3807,12 @@ def process_single_invoice_for_reference_data(token, invoice_id, source_type='pu 'message': invoice_details.get('message', 'Failed to get invoice details') } - # Создаем все доктайпы из одного инвойса - result = create_reference_data_from_single_invoice(invoice_details, source_type) + # Создаем доктайпы из одного инвойса (только выбранные типы) + result = create_reference_data_from_single_invoice( + invoice_details, source_type, + load_items=int(load_items), load_units=int(load_units), + load_customers=int(load_customers), load_suppliers=int(load_suppliers) + ) return result @@ -3818,178 +3824,182 @@ def process_single_invoice_for_reference_data(token, invoice_id, source_type='pu } @frappe.whitelist() -def create_reference_data_from_single_invoice(invoice_details, source_type): - """Создает все 4 типа доктайпов из одного инвойса с EQM кодами - оптимизированная версия""" +def create_reference_data_from_single_invoice(invoice_details, source_type, + load_items=1, load_units=1, + load_customers=1, load_suppliers=1): + """Создает доктайпы из одного инвойса с EQM кодами - оптимизированная версия""" try: stats = { 'items_created': 0, 'items_skipped': 0, - 'units_created': 0, + 'units_created': 0, 'units_skipped': 0, 'customers_created': 0, 'customers_skipped': 0, 'suppliers_created': 0, 'suppliers_skipped': 0 } - + serial_number = invoice_details.get('serialNumber', '') items = invoice_details.get('items', []) sender = invoice_details.get('sender', {}) receiver = invoice_details.get('receiver', {}) - + # === СОЗДАНИЕ ТОВАРОВ С EQM КОДАМИ === - processed_items = set() - for item in items: - item_name = item.get('productName', '').strip() - if not item_name: - continue - - # Обрезаем до 140 символов - if len(item_name) > 140: - item_name = item_name[:140] - - if item_name in processed_items: - continue - - processed_items.add(item_name) - - # Проверяем существование - if frappe.db.exists('E-Taxes Item', item_name): - stats['items_skipped'] += 1 - continue - - try: - # Создаем товар - item_code = item.get('itemId', '') or f"CODE-{serial_number}" - - # Обрабатываем группу товаров - product_group = item.get('productGroup', {}) - product_group_code = product_group.get('code', '') if product_group else '' - product_group_type = product_group.get('type', '') if product_group else '' - - # Получаем название группы товаров - product_group_name = '' - if product_group and product_group.get('name'): - names = product_group.get('name', {}) - if isinstance(names, dict): - product_group_name = names.get('az', '') or names.get('en', '') or names.get('ru', '') - elif isinstance(names, str): - product_group_name = names - - # Обрезаем название группы товаров до 1000 символов - if len(product_group_name) > 1000: - product_group_name = product_group_name[:1000] - - # НОВОЕ: Поиск EQM Code по коду группы товаров - возвращаем name для Link поля - eqm_code = None - if product_group_code: - try: - frappe.log_error(f"[EQM DEBUG] Searching for product_group_code: '{product_group_code}'", "EQM Code Search") - - # Ищем запись и получаем и name, и eqm_name - eqm_record = frappe.db.sql(""" - SELECT name, eqm_name - FROM `tabEQM Codes` - WHERE eqm_name LIKE %s - LIMIT 1 - """, (f"{product_group_code} -%",), as_dict=True) - - frappe.log_error(f"[EQM DEBUG] SQL search result: {eqm_record}", "EQM Code Search") - - if eqm_record: - # ИСПРАВЛЕНИЕ: Сохраняем name (ID записи) для Link поля, а не eqm_name - eqm_code = eqm_record[0].get('name') # Изменено с eqm_name на name - frappe.log_error(f"[EQM DEBUG] Found EQM Code ID: '{eqm_code}' (description: '{eqm_record[0].get('eqm_name')}') for product group '{product_group_code}' in item {item_name}", "EQM Code Assignment") - else: - frappe.log_error(f"[EQM DEBUG] EQM Code not found for product group '{product_group_code}' in item {item_name}", "EQM Code Not Found") - - except Exception as e: - frappe.log_error(f"[EQM DEBUG] Error searching EQM Code for '{product_group_code}': {str(e)}", "EQM Code Search Error") + if int(load_items): + processed_items = set() + for item in items: + item_name = item.get('productName', '').strip() + if not item_name: + continue - # Логируем финальное значение - frappe.log_error(f"[EQM DEBUG] Final eqm_code ID to save: '{eqm_code}' for item {item_name}", "EQM Code Final") + # Обрезаем до 140 символов + if len(item_name) > 140: + item_name = item_name[:140] + + if item_name in processed_items: + continue + + processed_items.add(item_name) + + # Проверяем существование + if frappe.db.exists('E-Taxes Item', item_name): + stats['items_skipped'] += 1 + continue + + try: + # Создаем товар + item_code = item.get('itemId', '') or f"CODE-{serial_number}" + + # Обрабатываем группу товаров + product_group = item.get('productGroup', {}) + product_group_code = product_group.get('code', '') if product_group else '' + product_group_type = product_group.get('type', '') if product_group else '' + + # Получаем название группы товаров + product_group_name = '' + if product_group and product_group.get('name'): + names = product_group.get('name', {}) + if isinstance(names, dict): + product_group_name = names.get('az', '') or names.get('en', '') or names.get('ru', '') + elif isinstance(names, str): + product_group_name = names + + # Обрезаем название группы товаров до 1000 символов + if len(product_group_name) > 1000: + product_group_name = product_group_name[:1000] + + # НОВОЕ: Поиск EQM Code по коду группы товаров - возвращаем name для Link поля + eqm_code = None + if product_group_code: + try: + frappe.log_error(f"[EQM DEBUG] Searching for product_group_code: '{product_group_code}'", "EQM Code Search") + + # Ищем запись и получаем и name, и eqm_name + eqm_record = frappe.db.sql(""" + SELECT name, eqm_name + FROM `tabEQM Codes` + WHERE eqm_name LIKE %s + LIMIT 1 + """, (f"{product_group_code} -%",), as_dict=True) + + frappe.log_error(f"[EQM DEBUG] SQL search result: {eqm_record}", "EQM Code Search") + + if eqm_record: + # ИСПРАВЛЕНИЕ: Сохраняем name (ID записи) для Link поля, а не eqm_name + eqm_code = eqm_record[0].get('name') # Изменено с eqm_name на name + frappe.log_error(f"[EQM DEBUG] Found EQM Code ID: '{eqm_code}' (description: '{eqm_record[0].get('eqm_name')}') for product group '{product_group_code}' in item {item_name}", "EQM Code Assignment") + else: + frappe.log_error(f"[EQM DEBUG] EQM Code not found for product group '{product_group_code}' in item {item_name}", "EQM Code Not Found") + + except Exception as e: + frappe.log_error(f"[EQM DEBUG] Error searching EQM Code for '{product_group_code}': {str(e)}", "EQM Code Search Error") + + # Логируем финальное значение + frappe.log_error(f"[EQM DEBUG] Final eqm_code ID to save: '{eqm_code}' for item {item_name}", "EQM Code Final") + + # Логируем финальное значение + frappe.log_error(f"[EQM DEBUG] Final eqm_code value to save: '{eqm_code}' for item {item_name}", "EQM Code Final") + + # Определяем, является ли товар услугой + is_service = 1 if product_group_type == 'service' else 0 + + # Определяем источники + is_from_purchase = 1 if source_type == 'purchase' else 0 + is_from_sales = 1 if source_type == 'sales' else 0 + + # Создаем документ с EQM кодом + doc_data = { + 'doctype': 'E-Taxes Item', + 'etaxes_item_name': item_name, + 'etaxes_item_code': item_code, + 'etaxes_unit': item.get('unit', ''), + 'etaxes_price': item.get('pricePerUnit', 0), + 'etaxes_product_group_code': product_group_code, + 'etaxes_product_group_name': product_group_name, + 'etaxes_product_group_type': product_group_type, + 'is_service_item': is_service, + 'source_invoice': serial_number, + 'is_from_purchase': is_from_purchase, + 'is_from_sales': is_from_sales, + 'status': 'New' + } + + # НОВОЕ: Добавляем EQM код если найден + if eqm_code: + doc_data['eqm_code'] = eqm_code + + doc = frappe.get_doc(doc_data) + doc.insert(ignore_permissions=True, ignore_if_duplicate=True) + stats['items_created'] += 1 + + except frappe.DuplicateEntryError: + stats['items_skipped'] += 1 + except Exception as e: + frappe.log_error(f"Error creating E-Taxes Item {item_name}: {str(e)}", "Create Item Error") - # Логируем финальное значение - frappe.log_error(f"[EQM DEBUG] Final eqm_code value to save: '{eqm_code}' for item {item_name}", "EQM Code Final") - - # Определяем, является ли товар услугой - is_service = 1 if product_group_type == 'service' else 0 - - # Определяем источники - is_from_purchase = 1 if source_type == 'purchase' else 0 - is_from_sales = 1 if source_type == 'sales' else 0 - - # Создаем документ с EQM кодом - doc_data = { - 'doctype': 'E-Taxes Item', - 'etaxes_item_name': item_name, - 'etaxes_item_code': item_code, - 'etaxes_unit': item.get('unit', ''), - 'etaxes_price': item.get('pricePerUnit', 0), - 'etaxes_product_group_code': product_group_code, - 'etaxes_product_group_name': product_group_name, - 'etaxes_product_group_type': product_group_type, - 'is_service_item': is_service, - 'source_invoice': serial_number, - 'is_from_purchase': is_from_purchase, - 'is_from_sales': is_from_sales, - 'status': 'New' - } - - # НОВОЕ: Добавляем EQM код если найден - if eqm_code: - doc_data['eqm_code'] = eqm_code - - doc = frappe.get_doc(doc_data) - doc.insert(ignore_permissions=True, ignore_if_duplicate=True) - stats['items_created'] += 1 - - except frappe.DuplicateEntryError: - stats['items_skipped'] += 1 - except Exception as e: - frappe.log_error(f"Error creating E-Taxes Item {item_name}: {str(e)}", "Create Item Error") - # === СОЗДАНИЕ ЕДИНИЦ === - processed_units = set() - for item in items: - unit_name = item.get('unit', '').strip() - if not unit_name or unit_name in processed_units: - continue - - processed_units.add(unit_name) - - # Проверяем существование - if frappe.db.exists('E-Taxes Unit', unit_name): - stats['units_skipped'] += 1 - continue - - try: - source_type_text = 'Purchase' if source_type == 'purchase' else 'Sales' - - doc = frappe.get_doc({ - 'doctype': 'E-Taxes Unit', - 'etaxes_unit_name': unit_name, - 'etaxes_unit_code': unit_name, - 'source_invoice': serial_number, - 'source_type': source_type_text, - 'status': 'New' - }) - doc.insert(ignore_permissions=True, ignore_if_duplicate=True) - stats['units_created'] += 1 - - except frappe.DuplicateEntryError: - stats['units_skipped'] += 1 - except Exception as e: - frappe.log_error(f"Error creating E-Taxes Unit {unit_name}: {str(e)}", "Create Unit Error") - + if int(load_units): + processed_units = set() + for item in items: + unit_name = item.get('unit', '').strip() + if not unit_name or unit_name in processed_units: + continue + + processed_units.add(unit_name) + + # Проверяем существование + if frappe.db.exists('E-Taxes Unit', unit_name): + stats['units_skipped'] += 1 + continue + + try: + source_type_text = 'Purchase' if source_type == 'purchase' else 'Sales' + + doc = frappe.get_doc({ + 'doctype': 'E-Taxes Unit', + 'etaxes_unit_name': unit_name, + 'etaxes_unit_code': unit_name, + 'source_invoice': serial_number, + 'source_type': source_type_text, + 'status': 'New' + }) + doc.insert(ignore_permissions=True, ignore_if_duplicate=True) + stats['units_created'] += 1 + + except frappe.DuplicateEntryError: + stats['units_skipped'] += 1 + except Exception as e: + frappe.log_error(f"Error creating E-Taxes Unit {unit_name}: {str(e)}", "Create Unit Error") + # === СОЗДАНИЕ КЛИЕНТОВ (RECEIVER) === - if receiver: + if int(load_customers) and receiver: receiver_name = (receiver.get('name') or '').strip() receiver_tin = (receiver.get('tin') or '').strip() receiver_address = (receiver.get('address') or '').strip() - + receiver_name = ' '.join(receiver_name.split()) # Убираем лишние пробелы - + if receiver_name and receiver_tin: if not frappe.db.exists('E-Taxes Customers', receiver_name): try: @@ -4004,24 +4014,24 @@ def create_reference_data_from_single_invoice(invoice_details, source_type): }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) stats['customers_created'] += 1 - + except frappe.DuplicateEntryError: stats['customers_skipped'] += 1 except Exception as e: frappe.log_error(f"Error creating E-Taxes Customers {receiver_name}: {str(e)}", "Create Customer Error") else: stats['customers_skipped'] += 1 - + # === СОЗДАНИЕ ПОСТАВЩИКОВ/КЛИЕНТОВ (SENDER) === if sender: sender_name = (sender.get('name') or '').strip() sender_tin = (sender.get('tin') or '').strip() sender_address = (sender.get('address') or '').strip() - + sender_name = ' '.join(sender_name.split()) # Убираем лишние пробелы - + if sender_name and sender_tin: - if source_type == 'purchase': + if source_type == 'purchase' and int(load_suppliers): # Sender = Supplier для purchase инвойсов if not frappe.db.exists('E-Taxes Suppliers', sender_name): try: @@ -4036,14 +4046,14 @@ def create_reference_data_from_single_invoice(invoice_details, source_type): }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) stats['suppliers_created'] += 1 - + except frappe.DuplicateEntryError: stats['suppliers_skipped'] += 1 except Exception as e: frappe.log_error(f"Error creating E-Taxes Suppliers {sender_name}: {str(e)}", "Create Supplier Error") else: stats['suppliers_skipped'] += 1 - else: + elif source_type != 'purchase' and int(load_customers): # Sender = Customer для sales инвойсов if not frappe.db.exists('E-Taxes Customers', sender_name): try: @@ -4058,7 +4068,7 @@ def create_reference_data_from_single_invoice(invoice_details, source_type): }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) stats['customers_created'] += 1 - + except frappe.DuplicateEntryError: stats['customers_skipped'] += 1 except Exception as e: diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js index 8e60f0f..a56e924 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js +++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js @@ -983,7 +983,7 @@ function start_reference_data_loading(frm, values) { start_staged_invoice_loading(frm, values); } -function process_invoices_for_reference_data(invoices, token, accumulated_data, current_index = 0, frm) { +function process_invoices_for_reference_data(invoices, token, accumulated_data, current_index = 0, frm, load_flags = null) { // Инициализация при первом вызове if (current_index === 0) { window.cancelReferenceLoading = false; @@ -1050,7 +1050,11 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, args: { 'token': token, 'invoice_id': invoice_id, - 'source_type': source_type + 'source_type': source_type, + 'load_items': load_flags ? load_flags.load_items : 1, + 'load_units': load_flags ? load_flags.load_units : 1, + 'load_customers': load_flags ? load_flags.load_customers : 1, + 'load_suppliers': load_flags ? load_flags.load_suppliers : 1 }, callback: function(r) { if (r.message) { @@ -1071,7 +1075,7 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, token = token_r.message.token; setTimeout(function() { if (!window.cancelReferenceLoading) { - process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm); + process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm, load_flags); } }, 100); } else { @@ -1083,7 +1087,7 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, }); return; // Выходим, чтобы не продолжать со старым токеном } - + // Обработка успешного результата if (r.message.success && r.message.stats) { const stats = r.message.stats; @@ -1097,14 +1101,14 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, accumulated_data.suppliers_skipped += stats.suppliers_skipped || 0; } } - + // Переходим к следующему инвойсу setTimeout(function() { if (!window.cancelReferenceLoading) { - process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm); + process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm, load_flags); } }, 50); - + }, error: function(xhr, status, error) { // При ошибке сети пробуем обновить токен @@ -1117,14 +1121,14 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, token = token_r.message.token; setTimeout(function() { if (!window.cancelReferenceLoading) { - process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm); + process_invoices_for_reference_data(invoices, token, accumulated_data, current_index, frm, load_flags); } }, 100); } else { // Переходим к следующему инвойсу setTimeout(function() { if (!window.cancelReferenceLoading) { - process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm); + process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm, load_flags); } }, 100); } @@ -1134,7 +1138,7 @@ function process_invoices_for_reference_data(invoices, token, accumulated_data, // При других ошибках переходим к следующему инвойсу setTimeout(function() { if (!window.cancelReferenceLoading) { - process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm); + process_invoices_for_reference_data(invoices, token, accumulated_data, current_index + 1, frm, load_flags); } }, 100); } @@ -2174,7 +2178,11 @@ function start_staged_invoice_loading(frm, values) { inbox_completed: false, outbox_completed: false, token: null, - last_update_count: 0 // Для отслеживания когда показывать обновление + last_update_count: 0, // Для отслеживания когда показывать обновление + load_items: values.load_items ? 1 : 0, + load_units: values.load_units ? 1 : 0, + load_customers: values.load_customers ? 1 : 0, + load_suppliers: values.load_suppliers ? 1 : 0 }; window.cancelReferenceLoading = false; @@ -2344,7 +2352,12 @@ function check_loading_completion(loading_state, frm) { suppliers_created: 0, suppliers_skipped: 0, total_invoices: loading_state.all_invoices.length - }, 0, frm); + }, 0, frm, { + load_items: loading_state.load_items, + load_units: loading_state.load_units, + load_customers: loading_state.load_customers, + load_suppliers: loading_state.load_suppliers + }); }, 2000); } }