diff --git a/invoice_az/api.py b/invoice_az/api.py index ee5b42f..d57812a 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -1300,7 +1300,7 @@ def load_items_from_invoices(date_from, date_to, max_count=200, offset=0): for item in items: item_name = item.get('productName', '') - item_code = item.get('itemId', '') or f"CODE-{serial_number}-{processed_count}" + item_code = item.get('itemId', '') or serial_number # Skip empty items if not item_name: @@ -2252,39 +2252,35 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'message': 'No active E-Taxes settings found' } - # Create mapping dictionaries + # Create mapping dictionaries - ИСПРАВЛЕНО: по ID документов item_mappings = {} for mapping in settings.item_mappings: - # mapping.etaxes_item_name содержит name документа E-Taxes Item if mapping.etaxes_item_name and mapping.erp_item: item_mappings[mapping.etaxes_item_name] = mapping.erp_item party_mappings = {} for mapping in settings.party_mappings: if mapping.etaxes_party_name and mapping.erp_party: + # Ключ остается по имени и TIN, но значение - это erp_party key = f"{mapping.etaxes_party_name.lower()}|{mapping.etaxes_tax_id.lower() if mapping.etaxes_tax_id else ''}" party_mappings[key] = (mapping.erp_party, mapping.party_type) unit_mappings = {} for mapping in settings.unit_mappings: if mapping.etaxes_unit_name and mapping.erp_unit: - # mapping.etaxes_unit_name содержит name документа E-Taxes Unit unit_mappings[mapping.etaxes_unit_name] = mapping.erp_unit # Get default warehouse default_warehouse = warehouse if not default_warehouse: - # If warehouse not specified, try to get from settings if hasattr(settings, 'default_warehouse') and settings.default_warehouse: default_warehouse = settings.default_warehouse else: - # If warehouse not specified in settings, try to get default warehouse for company company = frappe.defaults.get_user_default('Company') if company: default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse') - # If still no warehouse, take first active warehouse if not default_warehouse: warehouses = frappe.get_all('Warehouse', filters={'is_group': 0, 'disabled': 0}, @@ -2293,7 +2289,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule if warehouses: default_warehouse = warehouses[0].name - # Check warehouse existence if not default_warehouse: return { 'success': False, @@ -2304,30 +2299,49 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule if purchase_order_name: po = frappe.get_doc('Purchase Order', purchase_order_name) else: - # Create new Purchase Order po = frappe.new_doc('Purchase Order') - # Set supplier + # Set supplier - ИСПРАВЛЕНО: поиск через E-Taxes Parties sender = invoice_data.get('sender', {}) - sender_key = f"{sender.get('name', '').lower()}|{sender.get('tin', '').lower()}" + sender_name = sender.get('name', '') + sender_tin = sender.get('tin', '') - if sender_key in party_mappings and party_mappings[sender_key][0]: - po.supplier = party_mappings[sender_key][0] - else: + # Ищем E-Taxes Parties документ + supplier_found = False + if sender_name and sender_tin: + etaxes_parties = frappe.get_all('E-Taxes Parties', + filters={ + 'etaxes_party_name': sender_name, + 'etaxes_tax_id': sender_tin + }, + fields=['name'], + limit=1) + + if etaxes_parties: + etaxes_party_name = etaxes_parties[0].name + # Ищем маппинг для этого документа + for mapping in settings.party_mappings: + if (mapping.etaxes_party_name == sender_name and + mapping.etaxes_tax_id == sender_tin and + mapping.erp_party): + po.supplier = mapping.erp_party + supplier_found = True + break + + if not supplier_found: return { 'success': False, 'unmatched_parties': [{ - 'name': sender.get('name', ''), - 'tin': sender.get('tin', ''), + 'name': sender_name, + 'tin': sender_tin, 'type': 'Sender' }], - 'message': f'No mapping found for supplier: {sender.get("name", "")}' + 'message': f'No mapping found for supplier: {sender_name}' } # Set dates po.transaction_date = frappe.utils.today() - # Get date from invoice or parameter if specified if schedule_date: po.schedule_date = schedule_date elif invoice_data.get("creationDate"): @@ -2341,7 +2355,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule po.title = f"Invoice {invoice_data.get('serialNumber', '')}" po.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "") - # Clear existing items if need to update them completely if purchase_order_name and invoice_data.get("items"): po.items = [] @@ -2361,17 +2374,21 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule # Add items from invoice if invoice_data.get("items"): for item in invoice_data.get("items", []): - # Подготавливаем данные товара item_name = item.get("productName", "") item_code = item.get("itemId", "") - # ИСПРАВЛЕНО: Ищем соответствие товара - # Так как autoname = "field:etaxes_item_name", name документа E-Taxes Item = etaxes_item_name - # Поэтому проверяем напрямую по item_name в mappings - mapped_item = item_mappings.get(item_name) + # ИСПРАВЛЕНО: Ищем E-Taxes Item по названию товара, затем маппинг по ID документа + mapped_item = None + etaxes_items = frappe.get_all('E-Taxes Item', + filters={'etaxes_item_name': item_name}, + fields=['name'], + limit=1) + + if etaxes_items: + etaxes_item_name = etaxes_items[0].name + mapped_item = item_mappings.get(etaxes_item_name) if not mapped_item: - # Если соответствие не найдено, добавляем в список несопоставленных unmatched_items.append({ 'name': item_name, 'code': item_code @@ -2383,14 +2400,18 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule mapped_uom = None if unit_name: - # Так как autoname = "field:etaxes_unit_name", name документа E-Taxes Unit = etaxes_unit_name - # Поэтому проверяем напрямую по unit_name в mappings - mapped_uom = unit_mappings.get(unit_name) + # Ищем E-Taxes Unit по названию единицы, затем маппинг по ID документа + etaxes_units = frappe.get_all('E-Taxes Unit', + filters={'etaxes_unit_name': unit_name}, + fields=['name'], + limit=1) + + if etaxes_units: + etaxes_unit_name = etaxes_units[0].name + mapped_uom = unit_mappings.get(etaxes_unit_name) - # Если соответствие единицы не найдено, получаем UOM из товара if not mapped_uom: if unit_name: - # Добавляем в список несопоставленных единиц unmatched_units.append({ 'name': unit_name }) @@ -2400,7 +2421,7 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule item_doc = frappe.get_doc('Item', mapped_item) mapped_uom = item_doc.stock_uom except: - mapped_uom = "Nos" # Fallback + mapped_uom = "Nos" # Add position to PO po_item = frappe.new_doc("Purchase Order Item") @@ -2410,13 +2431,11 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule po_item.item_code = mapped_item - # ИСПРАВЛЕНИЕ: Получаем название и описание из базы данных Item try: item_doc = frappe.get_doc('Item', mapped_item) po_item.item_name = item_doc.item_name po_item.description = item_doc.description or item_doc.item_name except: - # Fallback на название из E-Taxes если не удалось получить из базы po_item.item_name = item.get("productName", "") po_item.description = item.get("productName", "") @@ -2424,11 +2443,7 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule po_item.rate = item.get("pricePerUnit", 0) po_item.amount = item.get("cost", 0) po_item.uom = mapped_uom - - # IMPORTANT: Set schedule_date for each row po_item.schedule_date = date_to_use - - # IMPORTANT: Set default warehouse po_item.warehouse = default_warehouse po.append("items", po_item) @@ -2450,7 +2465,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'message': f'No mapping found for {len(unmatched_units)} units' } - # Check that there is at least one element in table if added_items_count == 0: frappe.log_error( f"No items were added to PO. Invoice data: {invoice_data}", @@ -2471,25 +2485,20 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule if not item.warehouse: item.warehouse = default_warehouse - # Save again to ensure changes are applied po.save() - # ИЗМЕНЕНИЕ: Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а + # Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а invoice_id = invoice_data.get('id', '') serial_number = invoice_data.get('serialNumber', '') sender_name = invoice_data.get('sender', {}).get('name', '') if invoice_data.get('sender') else '' total = invoice_data.get('totalAmount', 0) or invoice_data.get('amount', 0) - # Создаем E-Taxes Purchase etaxes_purchase_result = create_etaxes_purchase(invoice_id, date_to_use, sender_name, total) if etaxes_purchase_result and etaxes_purchase_result.get('success'): - # Устанавливаем поля E-Taxes ДО submit'а po.is_taxes_doc = 1 po.taxes_doc = etaxes_purchase_result.get('name') - # Сохраняем изменения po.save() - # ИЗМЕНЕНИЕ: Делаем Submit для Purchase Order try: po.submit() frappe.log_error(f"Purchase Order {po.name} submitted successfully", "Import Invoice Success") @@ -2500,11 +2509,9 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'message': f'Failed to submit Purchase Order: {str(e)}' } - # ИЗМЕНЕНИЕ: Создаем Purchase Invoice try: pi_name = create_purchase_invoice_from_order(po.name) if pi_name: - # Делаем Submit для Purchase Invoice pi = frappe.get_doc("Purchase Invoice", pi_name) pi.submit() frappe.log_error(f"Purchase Invoice {pi_name} created and submitted successfully", "Import Invoice Success") @@ -2516,7 +2523,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'purchase_invoice': pi_name } else: - # Если не удалось создать Purchase Invoice, всё равно возвращаем успех с Purchase Order return { 'success': True, 'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.', @@ -2524,7 +2530,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule } except Exception as e: frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}", "Submit PI Error") - # Если не удалось создать Purchase Invoice, всё равно возвращаем успех с Purchase Order return { 'success': True, 'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.', @@ -2537,7 +2542,7 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'success': False, 'message': "An unknown error occurred, please try again in a few minutes." } - + # Также нужно убрать создание новых записей из других функций: @frappe.whitelist() @@ -2628,7 +2633,7 @@ def load_items_from_invoices(date_from, date_to, max_count=200, offset=0): for item in items: item_name = item.get('productName', '') - item_code = item.get('itemId', '') or f"CODE-{serial_number}-{processed_count}" + item_code = item.get('itemId', '') or serial_number # Skip empty items if not item_name: diff --git a/invoice_az/client/purchase_order.js b/invoice_az/client/purchase_order.js index a6bb2cb..094721e 100644 --- a/invoice_az/client/purchase_order.js +++ b/invoice_az/client/purchase_order.js @@ -1,3 +1,6 @@ +// Глобальный массив для хранения ошибок загрузки +var loading_errors = []; + frappe.ui.form.on('Purchase Order', { refresh: function(frm) { // Проверяем, установлена ли галочка is_taxes_doc @@ -1053,8 +1056,14 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) { }); } -// Функция для загрузки выбранных инвойсов с прогресс-баром и механизмом отмены -function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count = 0) { +// Модифицированная функция загрузки выбранных инвойсов (исправлен счетчик прогресса) +function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count = 0, current_index = 0) { + // Сброс массива ошибок в начале загрузки + if (processed_count === 0 && current_index === 0) { + loading_errors = []; + window.total_invoices_to_process = invoice_ids.length; + } + // Удаляем существующие обработчики отмены, если они есть $(document).off('progress-cancel.loading_invoices'); @@ -1063,18 +1072,14 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co // Скрываем прогресс-бар frappe.hide_progress(); - // Все успешно загружено или загрузка отменена - let message = cancel_loading ? - __('Loading cancelled. Loaded ' + processed_count + ' invoices.') : - __('Loading completed. Loaded ' + processed_count + ' invoices.'); + // Показываем сводку результатов + let errors_count = loading_errors.length; + let total_invoices = window.total_invoices_to_process || processed_count; - let indicator = cancel_loading ? 'orange' : 'green'; + show_loading_summary(total_invoices, processed_count, errors_count); - frappe.msgprint({ - title: __('Import Result'), - indicator: indicator, - message: message - }); + // Сбрасываем счетчик + window.total_invoices_to_process = null; // Обновляем форму или список if (frm) { @@ -1090,29 +1095,30 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co if (cancel_loading) { // Если была отмена, пропускаем все оставшиеся инвойсы invoice_ids = []; - // Явно скрываем прогресс-бар перед вызовом frappe.hide_progress(); - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); return; } // Берем первый ID из списка var invoice_id = invoice_ids.shift(); var is_last_invoice = invoice_ids.length === 0; + var total_to_process = window.total_invoices_to_process || (processed_count + invoice_ids.length + 1); + + // ИСПРАВЛЕНО: используем current_index для корректного отображения прогресса + current_index++; // Обновляем прогресс-бар - frappe.show_progress(__('Importing Invoices'), processed_count, processed_count + invoice_ids.length + 1, - __('Loading invoice ') + (processed_count + 1) + __(' of ') + (processed_count + invoice_ids.length + 1), null, true); + frappe.show_progress(__('Importing Invoices'), current_index - 1, total_to_process, + __('Loading invoice ') + current_index + __(' of ') + total_to_process, null, true); - // Обработчик отмены загрузки - используем пространство имен для предотвращения дублирования + // Обработчик отмены загрузки $(document).on('progress-cancel.loading_invoices', function() { cancel_loading = true; frappe.show_alert({ message: __('Cancelling import... Finishing current invoice.'), indicator: 'orange' }, 3); - - // Скрываем прогресс-бар при отмене frappe.hide_progress(); }); @@ -1132,7 +1138,7 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co let senderName = r.message.sender ? r.message.sender.name : ''; let total = r.message.totalAmount || r.message.amount || 0; - frappe.show_progress(__('Importing Invoices'), processed_count, processed_count + invoice_ids.length + 1, + frappe.show_progress(__('Importing Invoices'), current_index - 1, total_to_process, __('Importing invoice: ') + serialNumber); // Импортируем данные инвойса с указанием даты и склада @@ -1146,13 +1152,12 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co }, callback: function(import_r) { if (import_r.message && import_r.message.success) { - // Показываем уведомление об успешном импорте + // Успешный импорт frappe.show_alert({ message: __('Invoice successfully imported: ') + serialNumber, indicator: 'green' }, 3); - // Получаем имя созданного Purchase Order let purchase_order_name = import_r.message.purchase_order; // Сохраняем информацию в E-Taxes Purchase @@ -1166,7 +1171,6 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co }, callback: function(etaxes_r) { if (etaxes_r.message && etaxes_r.message.success) { - // Устанавливаем связь между Purchase Order и E-Taxes Purchase frappe.call({ method: 'invoice_az.api.link_purchase_order_to_etaxes', args: { @@ -1174,76 +1178,57 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co 'etaxes_purchase': etaxes_r.message.name }, callback: function(link_r) { - // Увеличиваем счетчик обработанных инвойсов - processed_count++; + processed_count++; // УСПЕШНО обработан - // Если это был последний инвойс, явно скрываем прогресс-бар if (is_last_invoice) { frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); - // Показываем сообщение о завершении - frappe.msgprint({ - title: __('Import Result'), - indicator: 'green', - message: __('Loading completed. Loaded ' + processed_count + ' invoices.') - }); + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); - // Обновляем форму или список if (frm) { frm.reload_doc(); } else if (cur_list) { cur_list.refresh(); } - return; } - // Продолжаем с обработкой следующего инвойса setTimeout(function() { - // Если создан новый заказ, обновляем ссылку на документ if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) { frm = null; } - // Перед вызовом явно скрываем текущий прогресс-бар if (invoice_ids.length === 0) { frappe.hide_progress(); } - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); }, 300); } }); } else { - // Обрабатываем ошибку создания E-Taxes Purchase - frappe.show_alert({ - message: __('Error creating E-Taxes Purchase'), - indicator: 'red' - }, 3); + // Ошибка создания E-Taxes Purchase + add_loading_error(r.message, 'E-Taxes Purchase Creation Error', + etaxes_r.message ? etaxes_r.message.message : 'Failed to create E-Taxes Purchase record', { + server_response: etaxes_r.message + }); - // Всё равно увеличиваем счетчик и переходим к следующему - processed_count++; + // НЕ увеличиваем processed_count для ошибок - // Если это был последний инвойс, явно скрываем прогресс-бар if (is_last_invoice) { frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); - // Показываем сообщение о завершении - frappe.msgprint({ - title: __('Import Result'), - indicator: 'green', - message: __('Loading completed. Loaded ' + processed_count + ' invoices.') - }); + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); - // Обновляем форму или список if (frm) { frm.reload_doc(); } else if (cur_list) { cur_list.refresh(); } - return; } @@ -1252,291 +1237,638 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co frm = null; } - // Перед вызовом явно скрываем текущий прогресс-бар if (invoice_ids.length === 0) { frappe.hide_progress(); } - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); }, 300); } } }); - } else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties)) { - // Если есть несопоставленные элементы или контрагенты - frappe.hide_progress(); - $(document).off('progress-cancel.loading_invoices'); + } else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties || import_r.message.unmatched_units)) { + // Несопоставленные элементы + let error_type = ''; + let additional_data = { + invoice_items: r.message.items || [] + }; if (import_r.message.unmatched_items) { - show_unmatched_items_dialog(r.message, import_r.message.unmatched_items); + error_type = 'Unmapped Items'; + additional_data.unmatched_items = import_r.message.unmatched_items; } else if (import_r.message.unmatched_parties) { - show_unmatched_parties_dialog(r.message, import_r.message.unmatched_parties); + error_type = 'Unmapped Parties'; + additional_data.unmatched_parties = import_r.message.unmatched_parties; + } else if (import_r.message.unmatched_units) { + error_type = 'Unmapped Units'; + additional_data.unmatched_units = import_r.message.unmatched_units; } - // Останавливаем загрузку, так как требуется вмешательство пользователя - frappe.msgprint({ - title: __('Import stopped'), - indicator: 'red', - message: __('To continue, you need to create missing mappings') - }); - } else { - frappe.show_alert({ - message: __('Import error: ') + serialNumber + ' ' + import_r.message.message, - indicator: 'red' - }, 3); + additional_data.server_response = import_r.message; - // Если это был последний инвойс, явно скрываем прогресс-бар + add_loading_error(r.message, error_type, import_r.message.message, additional_data); + + // НЕ увеличиваем processed_count для ошибок + + // Продолжаем со следующим инвойсом if (is_last_invoice) { frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); - // Показываем сообщение о завершении - frappe.msgprint({ - title: __('Import Result'), - indicator: 'orange', - message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.') - }); + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); - // Обновляем форму или список if (frm) { frm.reload_doc(); } else if (cur_list) { cur_list.refresh(); } - return; } - // Переходим к следующему инвойсу, не увеличивая счетчик успешно обработанных setTimeout(function() { - // Перед вызовом явно скрываем текущий прогресс-бар if (invoice_ids.length === 0) { frappe.hide_progress(); } - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); + }, 300); + } else { + // Общая ошибка импорта + let error_message = import_r.message ? import_r.message.message : 'Unknown import error'; + let additional_data = { + invoice_items: r.message.items || [], + server_response: import_r.message + }; + + // Пытаемся получить более детальную информацию об ошибке + if (import_r.exc) { + additional_data.stack_trace = import_r.exc; + } + + add_loading_error(r.message, 'Import Error', error_message, additional_data); + + // НЕ увеличиваем processed_count для ошибок + + // Переходим к следующему инвойсу + if (is_last_invoice) { + frappe.hide_progress(); + $(document).off('progress-cancel.loading_invoices'); + + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); + + if (frm) { + frm.reload_doc(); + } else if (cur_list) { + cur_list.refresh(); + } + return; + } + + setTimeout(function() { + if (invoice_ids.length === 0) { + frappe.hide_progress(); + } + + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); }, 300); } + }, + error: function(xhr, status, error) { + // Ошибка вызова import_invoice_with_mapping + let error_message = 'Network error during import: ' + (error || 'Unknown network error'); + let additional_data = { + invoice_items: r.message.items || [], + xhr_status: xhr.status, + xhr_response: xhr.responseText + }; + + add_loading_error(r.message, 'Network Error', error_message, additional_data); + + // НЕ увеличиваем processed_count для ошибок + + if (is_last_invoice) { + frappe.hide_progress(); + $(document).off('progress-cancel.loading_invoices'); + + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); + + if (frm) { + frm.reload_doc(); + } else if (cur_list) { + cur_list.refresh(); + } + return; + } + + setTimeout(function() { + if (invoice_ids.length === 0) { + frappe.hide_progress(); + } + + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); + }, 300); } }); } else if (r.message && r.message.error === 'unauthorized') { - // Если ошибка авторизации, предлагаем авторизоваться заново + // Ошибка авторизации frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); frappe.confirm( __('Your E-Taxes session has expired. Would you like to authenticate now?'), function() { - // Запускаем процесс аутентификации и после успеха повторяем загрузку start_authentication_process(function() { - // После успешной аутентификации получаем новый токен и повторяем загрузку frappe.call({ method: 'invoice_az.api.get_default_asan_login', callback: function(login_r) { if (login_r.message && login_r.message.found && login_r.message.main_token) { - // Возвращаем текущий инвойс в начало списка invoice_ids.unshift(invoice_id); - - // Используем новый токен для загрузки - load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse, processed_count, current_index - 1); } else { - frappe.msgprint({ - title: __('Error'), - indicator: 'red', - message: __('Failed to get token after authentication') - }); + add_loading_error({id: invoice_id}, 'Authentication Error', + 'Failed to get token after authentication'); + + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); } } }); }); }, function() { - // Если пользователь отказался, показываем сообщение - frappe.show_alert({ - message: __('Authentication canceled. Operation cannot be completed.'), - indicator: 'red' - }, 5); + add_loading_error({id: invoice_id}, 'Authentication Cancelled', + 'User cancelled authentication process'); + + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); } ); } else { - frappe.show_alert({ - message: __('Error loading invoice data'), - indicator: 'red' - }, 3); + // Ошибка получения данных инвойса + let error_message = r.message ? r.message.message : 'Failed to load invoice details'; + add_loading_error({id: invoice_id}, 'Invoice Data Error', error_message, { + server_response: r.message + }); + + // НЕ увеличиваем processed_count для ошибок - // Если это был последний инвойс, явно скрываем прогресс-бар if (is_last_invoice) { frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); - // Показываем сообщение о завершении - frappe.msgprint({ - title: __('Import Result'), - indicator: 'orange', - message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.') - }); + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); - // Обновляем форму или список if (frm) { frm.reload_doc(); } else if (cur_list) { cur_list.refresh(); } - return; } - // Переходим к следующему инвойсу setTimeout(function() { - // Перед вызовом явно скрываем текущий прогресс-бар if (invoice_ids.length === 0) { frappe.hide_progress(); } - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); }, 300); } }, error: function(xhr, status, error) { - console.error("Error fetching invoice details:", error); + // Ошибка вызова get_invoice_details + let error_message = 'Network error loading invoice details: ' + (error || 'Unknown network error'); + add_loading_error({id: invoice_id}, 'Network Error', error_message, { + xhr_status: xhr.status, + xhr_response: xhr.responseText + }); + + // НЕ увеличиваем processed_count для ошибок - // Если это был последний инвойс, явно скрываем прогресс-бар if (is_last_invoice) { frappe.hide_progress(); $(document).off('progress-cancel.loading_invoices'); - // Показываем сообщение о завершении - frappe.msgprint({ - title: __('Import Result'), - indicator: 'orange', - message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.') - }); + let errors_count = loading_errors.length; + show_loading_summary(total_to_process, processed_count, errors_count); - // Обновляем форму или список if (frm) { frm.reload_doc(); } else if (cur_list) { cur_list.refresh(); } - return; } - // В случае ошибки сети пропускаем этот инвойс и переходим к следующему setTimeout(function() { - // Перед вызовом явно скрываем текущий прогресс-бар if (invoice_ids.length === 0) { frappe.hide_progress(); } - load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count); + load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count, current_index); }, 300); } }); } -// Функция для отображения диалога с несопоставленными элементами -function show_unmatched_items_dialog(invoice_data, unmatched_items) { - // Создаем таблицу с несопоставленными товарами - var items_table = '
' + __('To complete the import, you need to create mappings for the following items:') + '
' + - '| ' + __('Name') + ' | ' + - '' + __('Code') + ' | ' + - '
|---|---|
| ' + item.name + ' | ' + - '' + (item.code || '') + ' | ' + - '
' + __('To complete the import, you need to create mappings for the following partners:') + '
' + - '| ' + __('Name') + ' | ' + - '' + __('TIN/VOEN') + ' | ' + - '' + __('Type') + ' | ' + - '
|---|---|---|
| ' + party.name + ' | ' + - '' + (party.tin || '') + ' | ' + - '' + partyTypeDisplay + ' | ' + - '