added error log and bug fixes

This commit is contained in:
Ali 2025-05-26 22:41:42 +04:00
parent 468078a015
commit 49aad69efb
2 changed files with 601 additions and 264 deletions

View File

@ -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.',
@ -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:

View File

@ -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 = '<div class="text-center mb-3">' +
'<p>' + __('To complete the import, you need to create mappings for the following items:') + '</p>' +
'</div>' +
'<div style="max-height: 300px; overflow-y: auto;"><table class="table table-bordered">';
items_table += '<thead><tr>' +
'<th>' + __('Name') + '</th>' +
'<th>' + __('Code') + '</th>' +
'</tr></thead><tbody>';
function add_loading_error(invoice_data, error_type, error_message, additional_data = {}) {
let error_entry = {
invoice_id: invoice_data.id || 'Unknown',
serial_number: invoice_data.serialNumber || 'Unknown',
sender_name: invoice_data.sender ? invoice_data.sender.name : 'Unknown',
creation_date: invoice_data.creationDate || invoice_data.createdAt || invoice_data.date || 'Unknown', // ИСПРАВЛЕНО: добавлены альтернативы
error_type: error_type,
error_message: error_message,
timestamp: new Date().toISOString(),
...additional_data
};
unmatched_items.forEach(function(item) {
items_table += '<tr>' +
'<td>' + item.name + '</td>' +
'<td>' + (item.code || '') + '</td>' +
'</tr>';
});
items_table += '</tbody></table></div>';
// Создаем диалог
var d = new frappe.ui.Dialog({
title: __('Unmapped items'),
fields: [
{
fieldname: 'items_html',
fieldtype: 'HTML',
options: items_table
}
],
primary_action_label: __('Go to settings'),
primary_action: function() {
d.hide();
// Переходим к настройкам E-Taxes
frappe.set_route('Form', 'E-Taxes Settings');
},
secondary_action_label: __('Cancel'),
secondary_action: function() {
d.hide();
}
});
d.show();
loading_errors.push(error_entry);
console.error('E-Taxes Import Error:', error_entry);
}
// Функция для отображения диалога с несопоставленными контрагентами
function show_unmatched_parties_dialog(invoice_data, unmatched_parties) {
// Создаем таблицу с несопоставленными контрагентами
var parties_table = '<div class="text-center mb-3">' +
'<p>' + __('To complete the import, you need to create mappings for the following partners:') + '</p>' +
'</div>' +
'<div style="max-height: 300px; overflow-y: auto;"><table class="table table-bordered">';
parties_table += '<thead><tr>' +
'<th>' + __('Name') + '</th>' +
'<th>' + __('TIN/VOEN') + '</th>' +
'<th>' + __('Type') + '</th>' +
'</tr></thead><tbody>';
// Функция для отображения сводки ошибок
function show_loading_summary(total_invoices, processed_count, errors_count) {
let title = '';
let indicator = '';
let message = '';
unmatched_parties.forEach(function(party) {
// Преобразуем тип контрагента для отображения
let partyTypeDisplay = '';
if (party.type === 'Sender') {
partyTypeDisplay = 'Supplier (Sender)';
} else if (party.type === 'Receiver') {
partyTypeDisplay = 'Customer (Receiver)';
if (errors_count === 0) {
title = __('Import Completed Successfully');
indicator = 'green';
message = __('All ') + total_invoices + __(' invoices were imported successfully.');
frappe.msgprint({
title: title,
indicator: indicator,
message: message
});
} else {
partyTypeDisplay = party.type || '';
}
title = errors_count === total_invoices ? __('Import Failed') : __('Import Completed with Errors');
indicator = processed_count > 0 ? 'orange' : 'red';
parties_table += '<tr>' +
'<td>' + party.name + '</td>' +
'<td>' + (party.tin || '') + '</td>' +
'<td>' + partyTypeDisplay + '</td>' +
'</tr>';
message = '<div style="margin-bottom: 15px;">';
if (processed_count > 0) {
message += __('Successfully imported: ') + '<strong>' + processed_count + '</strong>' + __(' invoices') + '<br>';
}
message += __('Failed: ') + '<strong>' + errors_count + '</strong>' + __(' invoices');
message += '</div>';
message += '<div style="text-align: center;">' +
'<button class="btn btn-primary btn-sm" id="view-error-details-btn" style="margin-right: 10px;">' +
'<i class="fa fa-list"></i> ' + __('View Error Details') + '</button>' +
'<button class="btn btn-default btn-sm" id="export-error-log-btn">' +
'<i class="fa fa-download"></i> ' + __('Export CSV') + '</button>' +
'</div>';
let summary_dialog = frappe.msgprint({
title: title,
indicator: indicator,
message: message
});
parties_table += '</tbody></table></div>';
// Добавляем обработчики событий после создания диалога
setTimeout(function() {
$('#view-error-details-btn').off('click').on('click', function() {
summary_dialog.hide(); // ИЗМЕНЕНИЕ: Закрываем сводку перед открытием деталей
show_detailed_error_log();
});
$('#export-error-log-btn').off('click').on('click', function() {
export_error_log();
});
}, 200);
}
}
// Функция для отображения детального лога ошибок (полная версия)
function show_detailed_error_log() {
if (loading_errors.length === 0) {
frappe.msgprint(__('No errors to display'));
return;
}
let errors_html = '<div class="error-log-container">';
loading_errors.forEach(function(error, index) {
// ИСПРАВЛЕНО: обработка даты документа из E-Taxes
let document_date = 'No date';
if (error.creation_date && error.creation_date !== 'Unknown') {
try {
document_date = moment(error.creation_date).format('DD.MM.YYYY');
} catch (e) {
document_date = 'Invalid date';
}
}
errors_html += '<div class="error-item" style="margin-bottom: 20px; padding: 15px; border: 1px solid var(--border-color); border-radius: 6px; background: var(--bg-color);">';
// Заголовок ошибки
errors_html += '<div class="error-header" style="margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color);">';
errors_html += '<div style="display: flex; justify-content: space-between; align-items: center;">';
errors_html += '<h5 style="margin: 0; color: var(--text-color);">' +
'<strong>' + (error.serial_number || error.invoice_id) + '</strong></h5>';
errors_html += '<span class="label label-danger">' + error.error_type + '</span>';
errors_html += '</div>';
// ИСПРАВЛЕНО: убрана дата, оставлен только supplier
errors_html += '<small style="color: var(--text-muted);">' + error.sender_name + '</small>';
errors_html += '</div>';
// Сообщение об ошибке
errors_html += '<div class="error-message" style="margin-bottom: 10px;">';
errors_html += '<strong style="color: var(--text-color);">' + error.error_message + '</strong>';
errors_html += '</div>';
// Детали ошибки (только одна категория для избежания дублирования)
if (error.unmatched_items && error.unmatched_items.length > 0) {
errors_html += '<div class="error-details">';
errors_html += '<div style="font-weight: 600; margin-bottom: 5px; color: var(--text-color);">' + __('Unmapped items:') + '</div>';
errors_html += '<ul style="margin: 0; padding-left: 20px; color: var(--text-muted);">';
error.unmatched_items.forEach(function(item, idx) {
errors_html += '<li><strong>Row ' + (idx + 1) + ':</strong> ' + item.name;
if (item.code) {
errors_html += ' <em>(Code: ' + item.code + ')</em>';
}
errors_html += '</li>';
});
errors_html += '</ul></div>';
} else if (error.unmatched_parties && error.unmatched_parties.length > 0) {
errors_html += '<div class="error-details">';
errors_html += '<div style="font-weight: 600; margin-bottom: 5px; color: var(--text-color);">' + __('Unmapped parties:') + '</div>';
errors_html += '<ul style="margin: 0; padding-left: 20px; color: var(--text-muted);">';
error.unmatched_parties.forEach(function(party, idx) {
errors_html += '<li><strong>Party ' + (idx + 1) + ':</strong> ' + party.name;
if (party.tin) {
errors_html += ' <em>(TIN: ' + party.tin + ')</em>';
}
if (party.type) {
errors_html += ' <span class="label label-info" style="font-size: 10px;">' + party.type + '</span>';
}
errors_html += '</li>';
});
errors_html += '</ul></div>';
} else if (error.unmatched_units && error.unmatched_units.length > 0) {
errors_html += '<div class="error-details">';
errors_html += '<div style="font-weight: 600; margin-bottom: 5px; color: var(--text-color);">' + __('Unmapped units:') + '</div>';
errors_html += '<ul style="margin: 0; padding-left: 20px; color: var(--text-muted);">';
error.unmatched_units.forEach(function(unit, idx) {
errors_html += '<li><strong>Unit ' + (idx + 1) + ':</strong> ' + unit.name + '</li>';
});
errors_html += '</ul></div>';
} else if (error.invoice_items && error.invoice_items.length > 0) {
// Показываем items только если нет других unmapped категорий
errors_html += '<div class="error-details">';
errors_html += '<div style="font-weight: 600; margin-bottom: 5px; color: var(--text-color);">' + __('Invoice items:') + '</div>';
errors_html += '<ul style="margin: 0; padding-left: 20px; color: var(--text-muted);">';
error.invoice_items.forEach(function(item, idx) {
errors_html += '<li><strong>Row ' + (idx + 1) + ':</strong> ' + item.productName +
' <em>(Qty: ' + item.quantity + ', Price: ' + item.pricePerUnit + ')</em></li>';
});
errors_html += '</ul></div>';
}
// ИСПРАВЛЕНО: показываем дату документа из E-Taxes, а не timestamp
errors_html += '<div style="text-align: right; margin-top: 10px;">';
errors_html += '<small style="color: var(--text-muted);"><em>' + document_date + '</em></small>';
errors_html += '</div>';
errors_html += '</div>';
});
errors_html += '</div>';
// Группируем ошибки по типам для статистики
let error_types = {};
loading_errors.forEach(function(error) {
error_types[error.error_type] = (error_types[error.error_type] || 0) + 1;
});
// Простая статистика вверху
let stats_html = '<div style="margin-bottom: 20px; padding: 15px; background: var(--bg-light); border-radius: 6px; border: 1px solid var(--border-color);">';
stats_html += '<div style="display: flex; justify-content: space-between; align-items: center;">';
stats_html += '<div>';
stats_html += '<strong style="color: var(--text-color);">' + __('Total Errors:') + '</strong> ' + loading_errors.length + '<br>';
stats_html += '<span style="color: var(--text-muted);">';
Object.keys(error_types).forEach(function(type, index) {
if (index > 0) stats_html += ' • ';
stats_html += type + ': ' + error_types[type];
});
stats_html += '</span>';
stats_html += '</div>';
stats_html += '<div>';
stats_html += '<button class="btn btn-default btn-sm" id="export-csv-dialog-btn" style="margin-right: 5px;">' +
'<i class="fa fa-download"></i> ' + __('Export CSV') + '</button>';
stats_html += '<button class="btn btn-info btn-sm" id="copy-clipboard-dialog-btn" style="margin-right: 5px;">' +
'<i class="fa fa-copy"></i> ' + __('Copy') + '</button>';
stats_html += '<button class="btn btn-warning btn-sm" id="clear-log-dialog-btn">' +
'<i class="fa fa-trash"></i> ' + __('Clear') + '</button>';
stats_html += '</div></div>';
// Создаем диалог
var d = new frappe.ui.Dialog({
title: __('Unmapped partners'),
title: __('Error Details') + ' (' + loading_errors.length + ')',
size: 'large',
fields: [
{
fieldname: 'parties_html',
fieldname: 'stats_html',
fieldtype: 'HTML',
options: parties_table
options: stats_html
},
{
fieldname: 'errors_html',
fieldtype: 'HTML',
options: '<div style="max-height: 500px; overflow-y: auto;">' + errors_html + '</div>'
}
],
primary_action_label: __('Go to settings'),
primary_action_label: __('Close'),
primary_action: function() {
d.hide();
// Переходим к настройкам E-Taxes
frappe.set_route('Form', 'E-Taxes Settings');
},
secondary_action_label: __('Cancel'),
secondary_action: function() {
d.hide();
}
});
d.show();
// Добавляем обработчики событий после показа диалога
setTimeout(function() {
$('#export-csv-dialog-btn').off('click').on('click', function() {
export_error_log();
});
$('#copy-clipboard-dialog-btn').off('click').on('click', function() {
copy_error_log();
});
$('#clear-log-dialog-btn').off('click').on('click', function() {
clear_error_log();
d.hide(); // Закрываем диалог после очистки
});
}, 200);
}
// Функция для экспорта лога ошибок в CSV
function export_error_log() {
if (loading_errors.length === 0) {
frappe.msgprint(__('No errors to export'));
return;
}
try {
// Преобразуем ошибки в CSV формат
let csv_content = "data:text/csv;charset=utf-8,";
csv_content += "Invoice ID,Serial Number,Date,Supplier,Error Type,Error Message,Timestamp\n";
loading_errors.forEach(function(error) {
let clean_message = error.error_message.replace(/"/g, '""').replace(/\n/g, ' ').replace(/\r/g, ' ');
let clean_sender = error.sender_name.replace(/"/g, '""');
let row = [
error.invoice_id,
error.serial_number,
error.creation_date,
clean_sender,
error.error_type,
clean_message,
moment(error.timestamp).format('DD.MM.YYYY HH:mm:ss')
].map(function(field) {
return '"' + (field || '') + '"';
}).join(',');
csv_content += row + "\n";
});
// Создаем ссылку для скачивания
const encoded_uri = encodeURI(csv_content);
const link = document.createElement("a");
link.setAttribute("href", encoded_uri);
link.setAttribute("download", "etaxes_import_errors_" + moment().format('YYYY-MM-DD_HH-mm-ss') + ".csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
frappe.show_alert({
message: __('Error log exported successfully'),
indicator: 'green'
}, 3);
} catch (e) {
console.error('Export error:', e);
frappe.show_alert({
message: __('Failed to export error log'),
indicator: 'red'
}, 3);
}
}
// Функция для копирования лога ошибок в буфер обмена
function copy_error_log() {
if (loading_errors.length === 0) {
frappe.msgprint(__('No errors to copy'));
return;
}
try {
let text_content = "E-TAXES IMPORT ERROR LOG\n";
text_content += "Generated: " + moment().format('DD.MM.YYYY HH:mm:ss') + "\n";
text_content += "Total Errors: " + loading_errors.length + "\n\n";
loading_errors.forEach(function(error, index) {
text_content += "=== ERROR " + (index + 1) + " ===\n";
text_content += "Invoice: " + (error.serial_number || error.invoice_id) + "\n";
text_content += "Date: " + error.creation_date + "\n";
text_content += "Supplier: " + error.sender_name + "\n";
text_content += "Error Type: " + error.error_type + "\n";
text_content += "Message: " + error.error_message + "\n";
if (error.unmatched_items && error.unmatched_items.length > 0) {
text_content += "Unmapped Items:\n";
error.unmatched_items.forEach(function(item, idx) {
text_content += " - Row " + (idx + 1) + ": " + item.name +
(item.code ? " (Code: " + item.code + ")" : "") + "\n";
});
}
if (error.unmatched_parties && error.unmatched_parties.length > 0) {
text_content += "Unmapped Parties:\n";
error.unmatched_parties.forEach(function(party, idx) {
text_content += " - Party " + (idx + 1) + ": " + party.name +
(party.tin ? " (TIN: " + party.tin + ")" : "") + "\n";
});
}
if (error.unmatched_units && error.unmatched_units.length > 0) {
text_content += "Unmapped Units:\n";
error.unmatched_units.forEach(function(unit, idx) {
text_content += " - Unit " + (idx + 1) + ": " + unit.name + "\n";
});
}
text_content += "Timestamp: " + moment(error.timestamp).format('DD.MM.YYYY HH:mm:ss') + "\n\n";
});
// Копируем в буфер обмена
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text_content).then(function() {
frappe.show_alert({
message: __('Error log copied to clipboard'),
indicator: 'green'
}, 3);
}).catch(function(err) {
console.error('Could not copy text: ', err);
fallback_copy_to_clipboard(text_content);
});
} else {
fallback_copy_to_clipboard(text_content);
}
} catch (e) {
console.error('Copy error:', e);
frappe.show_alert({
message: __('Failed to copy to clipboard'),
indicator: 'red'
}, 3);
}
}
// Функция для очистки лога ошибок
function clear_error_log() {
frappe.confirm(
__('Are you sure you want to clear the error log?'),
function() {
loading_errors = [];
frappe.show_alert({
message: __('Error log cleared'),
indicator: 'green'
}, 2);
}
);
}
// Fallback функция для копирования в старых браузерах
function fallback_copy_to_clipboard(text) {
try {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
frappe.show_alert({
message: __('Error log copied to clipboard'),
indicator: 'green'
}, 3);
} else {
throw new Error('Copy command failed');
}
} catch (err) {
console.error('Fallback copy failed:', err);
frappe.show_alert({
message: __('Failed to copy to clipboard. Please copy manually.'),
indicator: 'red'
}, 3);
}
}