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:
|
for item in items:
|
||||||
item_name = item.get('productName', '')
|
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
|
# Skip empty items
|
||||||
if not item_name:
|
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'
|
'message': 'No active E-Taxes settings found'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create mapping dictionaries
|
# Create mapping dictionaries - ИСПРАВЛЕНО: по ID документов
|
||||||
item_mappings = {}
|
item_mappings = {}
|
||||||
for mapping in settings.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:
|
if mapping.etaxes_item_name and mapping.erp_item:
|
||||||
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||||
|
|
||||||
party_mappings = {}
|
party_mappings = {}
|
||||||
for mapping in settings.party_mappings:
|
for mapping in settings.party_mappings:
|
||||||
if mapping.etaxes_party_name and mapping.erp_party:
|
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 ''}"
|
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)
|
party_mappings[key] = (mapping.erp_party, mapping.party_type)
|
||||||
|
|
||||||
unit_mappings = {}
|
unit_mappings = {}
|
||||||
for mapping in settings.unit_mappings:
|
for mapping in settings.unit_mappings:
|
||||||
if mapping.etaxes_unit_name and mapping.erp_unit:
|
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
|
unit_mappings[mapping.etaxes_unit_name] = mapping.erp_unit
|
||||||
|
|
||||||
# Get default warehouse
|
# Get default warehouse
|
||||||
default_warehouse = warehouse
|
default_warehouse = warehouse
|
||||||
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
# If warehouse not specified, try to get from settings
|
|
||||||
if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
|
if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
|
||||||
default_warehouse = settings.default_warehouse
|
default_warehouse = settings.default_warehouse
|
||||||
else:
|
else:
|
||||||
# If warehouse not specified in settings, try to get default warehouse for company
|
|
||||||
company = frappe.defaults.get_user_default('Company')
|
company = frappe.defaults.get_user_default('Company')
|
||||||
if company:
|
if company:
|
||||||
default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
|
default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
|
||||||
|
|
||||||
# If still no warehouse, take first active warehouse
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
warehouses = frappe.get_all('Warehouse',
|
warehouses = frappe.get_all('Warehouse',
|
||||||
filters={'is_group': 0, 'disabled': 0},
|
filters={'is_group': 0, 'disabled': 0},
|
||||||
|
|
@ -2293,7 +2289,6 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
if warehouses:
|
if warehouses:
|
||||||
default_warehouse = warehouses[0].name
|
default_warehouse = warehouses[0].name
|
||||||
|
|
||||||
# Check warehouse existence
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
|
|
@ -2304,30 +2299,49 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
if purchase_order_name:
|
if purchase_order_name:
|
||||||
po = frappe.get_doc('Purchase Order', purchase_order_name)
|
po = frappe.get_doc('Purchase Order', purchase_order_name)
|
||||||
else:
|
else:
|
||||||
# Create new Purchase Order
|
|
||||||
po = frappe.new_doc('Purchase Order')
|
po = frappe.new_doc('Purchase Order')
|
||||||
|
|
||||||
# Set supplier
|
# Set supplier - ИСПРАВЛЕНО: поиск через E-Taxes Parties
|
||||||
sender = invoice_data.get('sender', {})
|
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]:
|
# Ищем E-Taxes Parties документ
|
||||||
po.supplier = party_mappings[sender_key][0]
|
supplier_found = False
|
||||||
else:
|
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 {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'unmatched_parties': [{
|
'unmatched_parties': [{
|
||||||
'name': sender.get('name', ''),
|
'name': sender_name,
|
||||||
'tin': sender.get('tin', ''),
|
'tin': sender_tin,
|
||||||
'type': 'Sender'
|
'type': 'Sender'
|
||||||
}],
|
}],
|
||||||
'message': f'No mapping found for supplier: {sender.get("name", "")}'
|
'message': f'No mapping found for supplier: {sender_name}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set dates
|
# Set dates
|
||||||
po.transaction_date = frappe.utils.today()
|
po.transaction_date = frappe.utils.today()
|
||||||
|
|
||||||
# Get date from invoice or parameter if specified
|
|
||||||
if schedule_date:
|
if schedule_date:
|
||||||
po.schedule_date = schedule_date
|
po.schedule_date = schedule_date
|
||||||
elif invoice_data.get("creationDate"):
|
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.title = f"Invoice {invoice_data.get('serialNumber', '')}"
|
||||||
po.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
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"):
|
if purchase_order_name and invoice_data.get("items"):
|
||||||
po.items = []
|
po.items = []
|
||||||
|
|
||||||
|
|
@ -2361,17 +2374,21 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
# Add items from invoice
|
# Add items from invoice
|
||||||
if invoice_data.get("items"):
|
if invoice_data.get("items"):
|
||||||
for item in invoice_data.get("items", []):
|
for item in invoice_data.get("items", []):
|
||||||
# Подготавливаем данные товара
|
|
||||||
item_name = item.get("productName", "")
|
item_name = item.get("productName", "")
|
||||||
item_code = item.get("itemId", "")
|
item_code = item.get("itemId", "")
|
||||||
|
|
||||||
# ИСПРАВЛЕНО: Ищем соответствие товара
|
# ИСПРАВЛЕНО: Ищем E-Taxes Item по названию товара, затем маппинг по ID документа
|
||||||
# Так как autoname = "field:etaxes_item_name", name документа E-Taxes Item = etaxes_item_name
|
mapped_item = None
|
||||||
# Поэтому проверяем напрямую по item_name в mappings
|
etaxes_items = frappe.get_all('E-Taxes Item',
|
||||||
mapped_item = item_mappings.get(item_name)
|
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:
|
if not mapped_item:
|
||||||
# Если соответствие не найдено, добавляем в список несопоставленных
|
|
||||||
unmatched_items.append({
|
unmatched_items.append({
|
||||||
'name': item_name,
|
'name': item_name,
|
||||||
'code': item_code
|
'code': item_code
|
||||||
|
|
@ -2383,14 +2400,18 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
mapped_uom = None
|
mapped_uom = None
|
||||||
|
|
||||||
if unit_name:
|
if unit_name:
|
||||||
# Так как autoname = "field:etaxes_unit_name", name документа E-Taxes Unit = etaxes_unit_name
|
# Ищем E-Taxes Unit по названию единицы, затем маппинг по ID документа
|
||||||
# Поэтому проверяем напрямую по unit_name в mappings
|
etaxes_units = frappe.get_all('E-Taxes Unit',
|
||||||
mapped_uom = unit_mappings.get(unit_name)
|
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 not mapped_uom:
|
||||||
if unit_name:
|
if unit_name:
|
||||||
# Добавляем в список несопоставленных единиц
|
|
||||||
unmatched_units.append({
|
unmatched_units.append({
|
||||||
'name': unit_name
|
'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)
|
item_doc = frappe.get_doc('Item', mapped_item)
|
||||||
mapped_uom = item_doc.stock_uom
|
mapped_uom = item_doc.stock_uom
|
||||||
except:
|
except:
|
||||||
mapped_uom = "Nos" # Fallback
|
mapped_uom = "Nos"
|
||||||
|
|
||||||
# Add position to PO
|
# Add position to PO
|
||||||
po_item = frappe.new_doc("Purchase Order Item")
|
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
|
po_item.item_code = mapped_item
|
||||||
|
|
||||||
# ИСПРАВЛЕНИЕ: Получаем название и описание из базы данных Item
|
|
||||||
try:
|
try:
|
||||||
item_doc = frappe.get_doc('Item', mapped_item)
|
item_doc = frappe.get_doc('Item', mapped_item)
|
||||||
po_item.item_name = item_doc.item_name
|
po_item.item_name = item_doc.item_name
|
||||||
po_item.description = item_doc.description or item_doc.item_name
|
po_item.description = item_doc.description or item_doc.item_name
|
||||||
except:
|
except:
|
||||||
# Fallback на название из E-Taxes если не удалось получить из базы
|
|
||||||
po_item.item_name = item.get("productName", "")
|
po_item.item_name = item.get("productName", "")
|
||||||
po_item.description = 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.rate = item.get("pricePerUnit", 0)
|
||||||
po_item.amount = item.get("cost", 0)
|
po_item.amount = item.get("cost", 0)
|
||||||
po_item.uom = mapped_uom
|
po_item.uom = mapped_uom
|
||||||
|
|
||||||
# IMPORTANT: Set schedule_date for each row
|
|
||||||
po_item.schedule_date = date_to_use
|
po_item.schedule_date = date_to_use
|
||||||
|
|
||||||
# IMPORTANT: Set default warehouse
|
|
||||||
po_item.warehouse = default_warehouse
|
po_item.warehouse = default_warehouse
|
||||||
|
|
||||||
po.append("items", po_item)
|
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'
|
'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:
|
if added_items_count == 0:
|
||||||
frappe.log_error(
|
frappe.log_error(
|
||||||
f"No items were added to PO. Invoice data: {invoice_data}",
|
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:
|
if not item.warehouse:
|
||||||
item.warehouse = default_warehouse
|
item.warehouse = default_warehouse
|
||||||
|
|
||||||
# Save again to ensure changes are applied
|
|
||||||
po.save()
|
po.save()
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а
|
# Создаем E-Taxes Purchase и устанавливаем связь ДО submit'а
|
||||||
invoice_id = invoice_data.get('id', '')
|
invoice_id = invoice_data.get('id', '')
|
||||||
serial_number = invoice_data.get('serialNumber', '')
|
serial_number = invoice_data.get('serialNumber', '')
|
||||||
sender_name = invoice_data.get('sender', {}).get('name', '') if invoice_data.get('sender') else ''
|
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)
|
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)
|
etaxes_purchase_result = create_etaxes_purchase(invoice_id, date_to_use, sender_name, total)
|
||||||
if etaxes_purchase_result and etaxes_purchase_result.get('success'):
|
if etaxes_purchase_result and etaxes_purchase_result.get('success'):
|
||||||
# Устанавливаем поля E-Taxes ДО submit'а
|
|
||||||
po.is_taxes_doc = 1
|
po.is_taxes_doc = 1
|
||||||
po.taxes_doc = etaxes_purchase_result.get('name')
|
po.taxes_doc = etaxes_purchase_result.get('name')
|
||||||
# Сохраняем изменения
|
|
||||||
po.save()
|
po.save()
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Делаем Submit для Purchase Order
|
|
||||||
try:
|
try:
|
||||||
po.submit()
|
po.submit()
|
||||||
frappe.log_error(f"Purchase Order {po.name} submitted successfully", "Import Invoice Success")
|
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)}'
|
'message': f'Failed to submit Purchase Order: {str(e)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем Purchase Invoice
|
|
||||||
try:
|
try:
|
||||||
pi_name = create_purchase_invoice_from_order(po.name)
|
pi_name = create_purchase_invoice_from_order(po.name)
|
||||||
if pi_name:
|
if pi_name:
|
||||||
# Делаем Submit для Purchase Invoice
|
|
||||||
pi = frappe.get_doc("Purchase Invoice", pi_name)
|
pi = frappe.get_doc("Purchase Invoice", pi_name)
|
||||||
pi.submit()
|
pi.submit()
|
||||||
frappe.log_error(f"Purchase Invoice {pi_name} created and submitted successfully", "Import Invoice Success")
|
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
|
'purchase_invoice': pi_name
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# Если не удалось создать Purchase Invoice, всё равно возвращаем успех с Purchase Order
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
|
'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:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}", "Submit PI Error")
|
frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}", "Submit PI Error")
|
||||||
# Если не удалось создать Purchase Invoice, всё равно возвращаем успех с Purchase Order
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
|
'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:
|
for item in items:
|
||||||
item_name = item.get('productName', '')
|
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
|
# Skip empty items
|
||||||
if not item_name:
|
if not item_name:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Глобальный массив для хранения ошибок загрузки
|
||||||
|
var loading_errors = [];
|
||||||
|
|
||||||
frappe.ui.form.on('Purchase Order', {
|
frappe.ui.form.on('Purchase Order', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
// Проверяем, установлена ли галочка is_taxes_doc
|
// Проверяем, установлена ли галочка 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');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
|
|
@ -1063,18 +1072,14 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
// Скрываем прогресс-бар
|
// Скрываем прогресс-бар
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
|
|
||||||
// Все успешно загружено или загрузка отменена
|
// Показываем сводку результатов
|
||||||
let message = cancel_loading ?
|
let errors_count = loading_errors.length;
|
||||||
__('Loading cancelled. Loaded ' + processed_count + ' invoices.') :
|
let total_invoices = window.total_invoices_to_process || processed_count;
|
||||||
__('Loading completed. Loaded ' + processed_count + ' invoices.');
|
|
||||||
|
|
||||||
let indicator = cancel_loading ? 'orange' : 'green';
|
show_loading_summary(total_invoices, processed_count, errors_count);
|
||||||
|
|
||||||
frappe.msgprint({
|
// Сбрасываем счетчик
|
||||||
title: __('Import Result'),
|
window.total_invoices_to_process = null;
|
||||||
indicator: indicator,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
// Обновляем форму или список
|
||||||
if (frm) {
|
if (frm) {
|
||||||
|
|
@ -1090,29 +1095,30 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
if (cancel_loading) {
|
if (cancel_loading) {
|
||||||
// Если была отмена, пропускаем все оставшиеся инвойсы
|
// Если была отмена, пропускаем все оставшиеся инвойсы
|
||||||
invoice_ids = [];
|
invoice_ids = [];
|
||||||
// Явно скрываем прогресс-бар перед вызовом
|
|
||||||
frappe.hide_progress();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Берем первый ID из списка
|
// Берем первый ID из списка
|
||||||
var invoice_id = invoice_ids.shift();
|
var invoice_id = invoice_ids.shift();
|
||||||
var is_last_invoice = invoice_ids.length === 0;
|
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,
|
frappe.show_progress(__('Importing Invoices'), current_index - 1, total_to_process,
|
||||||
__('Loading invoice ') + (processed_count + 1) + __(' of ') + (processed_count + invoice_ids.length + 1), null, true);
|
__('Loading invoice ') + current_index + __(' of ') + total_to_process, null, true);
|
||||||
|
|
||||||
// Обработчик отмены загрузки - используем пространство имен для предотвращения дублирования
|
// Обработчик отмены загрузки
|
||||||
$(document).on('progress-cancel.loading_invoices', function() {
|
$(document).on('progress-cancel.loading_invoices', function() {
|
||||||
cancel_loading = true;
|
cancel_loading = true;
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Cancelling import... Finishing current invoice.'),
|
message: __('Cancelling import... Finishing current invoice.'),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
// Скрываем прогресс-бар при отмене
|
|
||||||
frappe.hide_progress();
|
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 senderName = r.message.sender ? r.message.sender.name : '';
|
||||||
let total = r.message.totalAmount || r.message.amount || 0;
|
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);
|
__('Importing invoice: ') + serialNumber);
|
||||||
|
|
||||||
// Импортируем данные инвойса с указанием даты и склада
|
// Импортируем данные инвойса с указанием даты и склада
|
||||||
|
|
@ -1146,13 +1152,12 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
},
|
},
|
||||||
callback: function(import_r) {
|
callback: function(import_r) {
|
||||||
if (import_r.message && import_r.message.success) {
|
if (import_r.message && import_r.message.success) {
|
||||||
// Показываем уведомление об успешном импорте
|
// Успешный импорт
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Invoice successfully imported: ') + serialNumber,
|
message: __('Invoice successfully imported: ') + serialNumber,
|
||||||
indicator: 'green'
|
indicator: 'green'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
// Получаем имя созданного Purchase Order
|
|
||||||
let purchase_order_name = import_r.message.purchase_order;
|
let purchase_order_name = import_r.message.purchase_order;
|
||||||
|
|
||||||
// Сохраняем информацию в E-Taxes Purchase
|
// Сохраняем информацию в E-Taxes Purchase
|
||||||
|
|
@ -1166,7 +1171,6 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
},
|
},
|
||||||
callback: function(etaxes_r) {
|
callback: function(etaxes_r) {
|
||||||
if (etaxes_r.message && etaxes_r.message.success) {
|
if (etaxes_r.message && etaxes_r.message.success) {
|
||||||
// Устанавливаем связь между Purchase Order и E-Taxes Purchase
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'invoice_az.api.link_purchase_order_to_etaxes',
|
method: 'invoice_az.api.link_purchase_order_to_etaxes',
|
||||||
args: {
|
args: {
|
||||||
|
|
@ -1174,76 +1178,57 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
'etaxes_purchase': etaxes_r.message.name
|
'etaxes_purchase': etaxes_r.message.name
|
||||||
},
|
},
|
||||||
callback: function(link_r) {
|
callback: function(link_r) {
|
||||||
// Увеличиваем счетчик обработанных инвойсов
|
processed_count++; // УСПЕШНО обработан
|
||||||
processed_count++;
|
|
||||||
|
|
||||||
// Если это был последний инвойс, явно скрываем прогресс-бар
|
|
||||||
if (is_last_invoice) {
|
if (is_last_invoice) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
// Показываем сообщение о завершении
|
let errors_count = loading_errors.length;
|
||||||
frappe.msgprint({
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
title: __('Import Result'),
|
|
||||||
indicator: 'green',
|
|
||||||
message: __('Loading completed. Loaded ' + processed_count + ' invoices.')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else if (cur_list) {
|
} else if (cur_list) {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Продолжаем с обработкой следующего инвойса
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// Если создан новый заказ, обновляем ссылку на документ
|
|
||||||
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
||||||
frm = null;
|
frm = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Перед вызовом явно скрываем текущий прогресс-бар
|
|
||||||
if (invoice_ids.length === 0) {
|
if (invoice_ids.length === 0) {
|
||||||
frappe.hide_progress();
|
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);
|
}, 300);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Обрабатываем ошибку создания E-Taxes Purchase
|
// Ошибка создания E-Taxes Purchase
|
||||||
frappe.show_alert({
|
add_loading_error(r.message, 'E-Taxes Purchase Creation Error',
|
||||||
message: __('Error creating E-Taxes Purchase'),
|
etaxes_r.message ? etaxes_r.message.message : 'Failed to create E-Taxes Purchase record', {
|
||||||
indicator: 'red'
|
server_response: etaxes_r.message
|
||||||
}, 3);
|
});
|
||||||
|
|
||||||
// Всё равно увеличиваем счетчик и переходим к следующему
|
// НЕ увеличиваем processed_count для ошибок
|
||||||
processed_count++;
|
|
||||||
|
|
||||||
// Если это был последний инвойс, явно скрываем прогресс-бар
|
|
||||||
if (is_last_invoice) {
|
if (is_last_invoice) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
// Показываем сообщение о завершении
|
let errors_count = loading_errors.length;
|
||||||
frappe.msgprint({
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
title: __('Import Result'),
|
|
||||||
indicator: 'green',
|
|
||||||
message: __('Loading completed. Loaded ' + processed_count + ' invoices.')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else if (cur_list) {
|
} else if (cur_list) {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1252,291 +1237,638 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_co
|
||||||
frm = null;
|
frm = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Перед вызовом явно скрываем текущий прогресс-бар
|
|
||||||
if (invoice_ids.length === 0) {
|
if (invoice_ids.length === 0) {
|
||||||
frappe.hide_progress();
|
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);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties)) {
|
} else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties || import_r.message.unmatched_units)) {
|
||||||
// Если есть несопоставленные элементы или контрагенты
|
// Несопоставленные элементы
|
||||||
frappe.hide_progress();
|
let error_type = '';
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
let additional_data = {
|
||||||
|
invoice_items: r.message.items || []
|
||||||
|
};
|
||||||
|
|
||||||
if (import_r.message.unmatched_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) {
|
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Останавливаем загрузку, так как требуется вмешательство пользователя
|
additional_data.server_response = import_r.message;
|
||||||
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);
|
|
||||||
|
|
||||||
// Если это был последний инвойс, явно скрываем прогресс-бар
|
add_loading_error(r.message, error_type, import_r.message.message, additional_data);
|
||||||
|
|
||||||
|
// НЕ увеличиваем processed_count для ошибок
|
||||||
|
|
||||||
|
// Продолжаем со следующим инвойсом
|
||||||
if (is_last_invoice) {
|
if (is_last_invoice) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
// Показываем сообщение о завершении
|
let errors_count = loading_errors.length;
|
||||||
frappe.msgprint({
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
title: __('Import Result'),
|
|
||||||
indicator: 'orange',
|
|
||||||
message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else if (cur_list) {
|
} else if (cur_list) {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переходим к следующему инвойсу, не увеличивая счетчик успешно обработанных
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// Перед вызовом явно скрываем текущий прогресс-бар
|
|
||||||
if (invoice_ids.length === 0) {
|
if (invoice_ids.length === 0) {
|
||||||
frappe.hide_progress();
|
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);
|
}, 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') {
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
// Если ошибка авторизации, предлагаем авторизоваться заново
|
// Ошибка авторизации
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
frappe.confirm(
|
frappe.confirm(
|
||||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||||
function() {
|
function() {
|
||||||
// Запускаем процесс аутентификации и после успеха повторяем загрузку
|
|
||||||
start_authentication_process(function() {
|
start_authentication_process(function() {
|
||||||
// После успешной аутентификации получаем новый токен и повторяем загрузку
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'invoice_az.api.get_default_asan_login',
|
method: 'invoice_az.api.get_default_asan_login',
|
||||||
callback: function(login_r) {
|
callback: function(login_r) {
|
||||||
if (login_r.message && login_r.message.found && login_r.message.main_token) {
|
if (login_r.message && login_r.message.found && login_r.message.main_token) {
|
||||||
// Возвращаем текущий инвойс в начало списка
|
|
||||||
invoice_ids.unshift(invoice_id);
|
invoice_ids.unshift(invoice_id);
|
||||||
|
load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse, processed_count, current_index - 1);
|
||||||
// Используем новый токен для загрузки
|
|
||||||
load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse, processed_count);
|
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint({
|
add_loading_error({id: invoice_id}, 'Authentication Error',
|
||||||
title: __('Error'),
|
'Failed to get token after authentication');
|
||||||
indicator: 'red',
|
|
||||||
message: __('Failed to get token after authentication')
|
let errors_count = loading_errors.length;
|
||||||
});
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
// Если пользователь отказался, показываем сообщение
|
add_loading_error({id: invoice_id}, 'Authentication Cancelled',
|
||||||
frappe.show_alert({
|
'User cancelled authentication process');
|
||||||
message: __('Authentication canceled. Operation cannot be completed.'),
|
|
||||||
indicator: 'red'
|
let errors_count = loading_errors.length;
|
||||||
}, 5);
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
frappe.show_alert({
|
// Ошибка получения данных инвойса
|
||||||
message: __('Error loading invoice data'),
|
let error_message = r.message ? r.message.message : 'Failed to load invoice details';
|
||||||
indicator: 'red'
|
add_loading_error({id: invoice_id}, 'Invoice Data Error', error_message, {
|
||||||
}, 3);
|
server_response: r.message
|
||||||
|
});
|
||||||
|
|
||||||
|
// НЕ увеличиваем processed_count для ошибок
|
||||||
|
|
||||||
// Если это был последний инвойс, явно скрываем прогресс-бар
|
|
||||||
if (is_last_invoice) {
|
if (is_last_invoice) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
// Показываем сообщение о завершении
|
let errors_count = loading_errors.length;
|
||||||
frappe.msgprint({
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
title: __('Import Result'),
|
|
||||||
indicator: 'orange',
|
|
||||||
message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else if (cur_list) {
|
} else if (cur_list) {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переходим к следующему инвойсу
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// Перед вызовом явно скрываем текущий прогресс-бар
|
|
||||||
if (invoice_ids.length === 0) {
|
if (invoice_ids.length === 0) {
|
||||||
frappe.hide_progress();
|
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);
|
}, 300);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
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) {
|
if (is_last_invoice) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
$(document).off('progress-cancel.loading_invoices');
|
$(document).off('progress-cancel.loading_invoices');
|
||||||
|
|
||||||
// Показываем сообщение о завершении
|
let errors_count = loading_errors.length;
|
||||||
frappe.msgprint({
|
show_loading_summary(total_to_process, processed_count, errors_count);
|
||||||
title: __('Import Result'),
|
|
||||||
indicator: 'orange',
|
|
||||||
message: __('Loading completed with some errors. Loaded ' + processed_count + ' invoices.')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем форму или список
|
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else if (cur_list) {
|
} else if (cur_list) {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// В случае ошибки сети пропускаем этот инвойс и переходим к следующему
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// Перед вызовом явно скрываем текущий прогресс-бар
|
|
||||||
if (invoice_ids.length === 0) {
|
if (invoice_ids.length === 0) {
|
||||||
frappe.hide_progress();
|
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);
|
}, 300);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для отображения диалога с несопоставленными элементами
|
function add_loading_error(invoice_data, error_type, error_message, additional_data = {}) {
|
||||||
function show_unmatched_items_dialog(invoice_data, unmatched_items) {
|
let error_entry = {
|
||||||
// Создаем таблицу с несопоставленными товарами
|
invoice_id: invoice_data.id || 'Unknown',
|
||||||
var items_table = '<div class="text-center mb-3">' +
|
serial_number: invoice_data.serialNumber || 'Unknown',
|
||||||
'<p>' + __('To complete the import, you need to create mappings for the following items:') + '</p>' +
|
sender_name: invoice_data.sender ? invoice_data.sender.name : 'Unknown',
|
||||||
'</div>' +
|
creation_date: invoice_data.creationDate || invoice_data.createdAt || invoice_data.date || 'Unknown', // ИСПРАВЛЕНО: добавлены альтернативы
|
||||||
'<div style="max-height: 300px; overflow-y: auto;"><table class="table table-bordered">';
|
error_type: error_type,
|
||||||
items_table += '<thead><tr>' +
|
error_message: error_message,
|
||||||
'<th>' + __('Name') + '</th>' +
|
timestamp: new Date().toISOString(),
|
||||||
'<th>' + __('Code') + '</th>' +
|
...additional_data
|
||||||
'</tr></thead><tbody>';
|
};
|
||||||
|
|
||||||
unmatched_items.forEach(function(item) {
|
loading_errors.push(error_entry);
|
||||||
items_table += '<tr>' +
|
console.error('E-Taxes Import Error:', error_entry);
|
||||||
'<td>' + item.name + '</td>' +
|
}
|
||||||
'<td>' + (item.code || '') + '</td>' +
|
|
||||||
'</tr>';
|
// Функция для отображения сводки ошибок
|
||||||
|
function show_loading_summary(total_invoices, processed_count, errors_count) {
|
||||||
|
let title = '';
|
||||||
|
let indicator = '';
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
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 {
|
||||||
|
title = errors_count === total_invoices ? __('Import Failed') : __('Import Completed with Errors');
|
||||||
|
indicator = processed_count > 0 ? 'orange' : 'red';
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем обработчики событий после создания диалога
|
||||||
|
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>';
|
||||||
});
|
});
|
||||||
|
|
||||||
items_table += '</tbody></table></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({
|
var d = new frappe.ui.Dialog({
|
||||||
title: __('Unmapped items'),
|
title: __('Error Details') + ' (' + loading_errors.length + ')',
|
||||||
|
size: 'large',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: 'items_html',
|
fieldname: 'stats_html',
|
||||||
fieldtype: 'HTML',
|
fieldtype: 'HTML',
|
||||||
options: items_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() {
|
primary_action: function() {
|
||||||
d.hide();
|
d.hide();
|
||||||
|
|
||||||
// Переходим к настройкам E-Taxes
|
|
||||||
frappe.set_route('Form', 'E-Taxes Settings');
|
|
||||||
},
|
|
||||||
secondary_action_label: __('Cancel'),
|
|
||||||
secondary_action: function() {
|
|
||||||
d.hide();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
d.show();
|
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 show_unmatched_parties_dialog(invoice_data, unmatched_parties) {
|
function export_error_log() {
|
||||||
// Создаем таблицу с несопоставленными контрагентами
|
if (loading_errors.length === 0) {
|
||||||
var parties_table = '<div class="text-center mb-3">' +
|
frappe.msgprint(__('No errors to export'));
|
||||||
'<p>' + __('To complete the import, you need to create mappings for the following partners:') + '</p>' +
|
return;
|
||||||
'</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>';
|
|
||||||
|
|
||||||
unmatched_parties.forEach(function(party) {
|
try {
|
||||||
// Преобразуем тип контрагента для отображения
|
// Преобразуем ошибки в CSV формат
|
||||||
let partyTypeDisplay = '';
|
let csv_content = "data:text/csv;charset=utf-8,";
|
||||||
if (party.type === 'Sender') {
|
csv_content += "Invoice ID,Serial Number,Date,Supplier,Error Type,Error Message,Timestamp\n";
|
||||||
partyTypeDisplay = 'Supplier (Sender)';
|
|
||||||
} else if (party.type === 'Receiver') {
|
loading_errors.forEach(function(error) {
|
||||||
partyTypeDisplay = 'Customer (Receiver)';
|
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 {
|
} else {
|
||||||
partyTypeDisplay = party.type || '';
|
fallback_copy_to_clipboard(text_content);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
parties_table += '<tr>' +
|
console.error('Copy error:', e);
|
||||||
'<td>' + party.name + '</td>' +
|
frappe.show_alert({
|
||||||
'<td>' + (party.tin || '') + '</td>' +
|
message: __('Failed to copy to clipboard'),
|
||||||
'<td>' + partyTypeDisplay + '</td>' +
|
indicator: 'red'
|
||||||
'</tr>';
|
}, 3);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
parties_table += '</tbody></table></div>';
|
|
||||||
|
// Функция для очистки лога ошибок
|
||||||
// Создаем диалог
|
function clear_error_log() {
|
||||||
var d = new frappe.ui.Dialog({
|
frappe.confirm(
|
||||||
title: __('Unmapped partners'),
|
__('Are you sure you want to clear the error log?'),
|
||||||
fields: [
|
function() {
|
||||||
{
|
loading_errors = [];
|
||||||
fieldname: 'parties_html',
|
frappe.show_alert({
|
||||||
fieldtype: 'HTML',
|
message: __('Error log cleared'),
|
||||||
options: parties_table
|
indicator: 'green'
|
||||||
}
|
}, 2);
|
||||||
],
|
}
|
||||||
primary_action_label: __('Go to settings'),
|
);
|
||||||
primary_action: function() {
|
}
|
||||||
d.hide();
|
|
||||||
|
// Fallback функция для копирования в старых браузерах
|
||||||
// Переходим к настройкам E-Taxes
|
function fallback_copy_to_clipboard(text) {
|
||||||
frappe.set_route('Form', 'E-Taxes Settings');
|
try {
|
||||||
},
|
const textArea = document.createElement("textarea");
|
||||||
secondary_action_label: __('Cancel'),
|
textArea.value = text;
|
||||||
secondary_action: function() {
|
document.body.appendChild(textArea);
|
||||||
d.hide();
|
textArea.focus();
|
||||||
}
|
textArea.select();
|
||||||
});
|
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
d.show();
|
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