added error log and bug fixes
This commit is contained in:
parent
468078a015
commit
49aad69efb
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue