Small edits to make app good (read only, anti dublicate and etc)
This commit is contained in:
parent
e12efa781a
commit
905cfeac0c
|
|
@ -1049,8 +1049,6 @@ def load_items_from_invoices(date_from, date_to, max_count=200):
|
|||
def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||
"""Loading parties from invoices for a period"""
|
||||
try:
|
||||
frappe.log_error(f"=== НАЧАЛО ЗАГРУЗКИ КОНТРАГЕНТОВ ===\nПараметры: date_from={date_from}, date_to={date_to}, max_count={max_count}", "Party Load Start")
|
||||
|
||||
# Счетчики для отслеживания
|
||||
created_count = 0
|
||||
skipped_count = 0
|
||||
|
|
@ -1058,10 +1056,8 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
|
||||
# Получаем настройки по умолчанию
|
||||
asan_login_settings = get_default_asan_login()
|
||||
frappe.log_error(f"Asan Login settings: {asan_login_settings}", "Asan Login Details")
|
||||
|
||||
if not asan_login_settings.get('found'):
|
||||
frappe.log_error("Asan Login settings not found!", "Party Load Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'No Asan Login settings found'
|
||||
|
|
@ -1096,11 +1092,9 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
"types": ["current", "corrected"]
|
||||
}
|
||||
|
||||
frappe.log_error(f"Отправляем запрос на API с фильтрами: {json.dumps(filters)}", "API Request Filters")
|
||||
response = get_invoices(asan_login_settings['main_token'], json.dumps(filters))
|
||||
|
||||
if 'error' in response:
|
||||
frappe.log_error(f"API вернул ошибку: {response['error']}", "API Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': response['error']
|
||||
|
|
@ -1108,10 +1102,8 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
|
||||
# Extract data from response
|
||||
invoices_data = response.get('data', []) or response.get('invoices', [])
|
||||
frappe.log_error(f"Получено инвойсов: {len(invoices_data)}", "Invoices Count")
|
||||
|
||||
if len(invoices_data) == 0:
|
||||
frappe.log_error(f"API вернул пустой список инвойсов! Полный ответ API: {response}", "Empty Invoices List")
|
||||
return {
|
||||
'success': True,
|
||||
'created_count': 0,
|
||||
|
|
@ -1121,10 +1113,6 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
'message': 'No invoices found for the specified period'
|
||||
}
|
||||
|
||||
# Логируем структуру первого инвойса для отладки
|
||||
if invoices_data and len(invoices_data) > 0:
|
||||
frappe.log_error(f"Структура первого инвойса: {json.dumps(invoices_data[0])}", "First Invoice Structure")
|
||||
|
||||
# Для хранения уникальных контрагентов (поставщики и клиенты)
|
||||
unique_parties = {}
|
||||
|
||||
|
|
@ -1133,21 +1121,18 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
for invoice in invoices_data:
|
||||
processed_count += 1
|
||||
invoice_id = invoice.get('id', 'Нет ID')
|
||||
frappe.log_error(f"Обработка инвойса #{processed_count}, ID: {invoice_id}", f"Invoice {invoice_id}")
|
||||
|
||||
serial_number = invoice.get('serialNumber', '')
|
||||
|
||||
# Обработка отправителя (Sender -> Supplier)
|
||||
sender = invoice.get('sender', {})
|
||||
frappe.log_error(f"Поля sender в инвойсе {invoice_id}: {json.dumps(sender)}", f"Sender Data {invoice_id}")
|
||||
|
||||
if sender:
|
||||
sender_name = sender.get('name', '')
|
||||
sender_tin = sender.get('tin', '')
|
||||
sender_pin = sender.get('pin', '')
|
||||
sender_address = sender.get('address', '')
|
||||
|
||||
if not sender_name or not sender_tin:
|
||||
frappe.log_error(f"Пропуск sender с неполными данными: name={sender_name}, tin={sender_tin}", f"Skip Sender {invoice_id}")
|
||||
continue
|
||||
|
||||
# Создаем ключ для проверки уникальности
|
||||
|
|
@ -1157,27 +1142,22 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
unique_parties[sender_key] = {
|
||||
'etaxes_party_name': sender_name,
|
||||
'etaxes_tax_id': sender_tin,
|
||||
'etaxes_pin': sender_pin,
|
||||
'etaxes_address': sender_address,
|
||||
'etaxes_party_type': 'Sender',
|
||||
'erp_party_type': 'Supplier',
|
||||
'source_invoice': serial_number,
|
||||
'status': 'New'
|
||||
}
|
||||
frappe.log_error(f"Добавлен новый уникальный sender: {sender_key}", f"New Supplier {invoice_id}")
|
||||
else:
|
||||
frappe.log_error(f"Инвойс {invoice_id} не содержит данных sender!", f"No Sender {invoice_id}")
|
||||
|
||||
# Обработка получателя (Receiver -> Customer)
|
||||
receiver = invoice.get('receiver', {})
|
||||
frappe.log_error(f"Поля receiver в инвойсе {invoice_id}: {json.dumps(receiver)}", f"Receiver Data {invoice_id}")
|
||||
|
||||
if receiver:
|
||||
receiver_name = receiver.get('name', '')
|
||||
receiver_tin = receiver.get('tin', '')
|
||||
receiver_pin = receiver.get('pin', '')
|
||||
receiver_address = receiver.get('address', '')
|
||||
|
||||
if not receiver_name or not receiver_tin:
|
||||
frappe.log_error(f"Пропуск receiver с неполными данными: name={receiver_name}, tin={receiver_tin}", f"Skip Receiver {invoice_id}")
|
||||
continue
|
||||
|
||||
# Создаем ключ для проверки уникальности
|
||||
|
|
@ -1187,27 +1167,12 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
unique_parties[receiver_key] = {
|
||||
'etaxes_party_name': receiver_name,
|
||||
'etaxes_tax_id': receiver_tin,
|
||||
'etaxes_pin': receiver_pin,
|
||||
'etaxes_address': receiver_address,
|
||||
'etaxes_party_type': 'Receiver',
|
||||
'erp_party_type': 'Customer',
|
||||
'source_invoice': serial_number,
|
||||
'status': 'New'
|
||||
}
|
||||
frappe.log_error(f"Добавлен новый уникальный receiver: {receiver_key}", f"New Customer {invoice_id}")
|
||||
else:
|
||||
frappe.log_error(f"Инвойс {invoice_id} не содержит данных receiver!", f"No Receiver {invoice_id}")
|
||||
|
||||
frappe.log_error(f"Обработано инвойсов: {processed_count}, Найдено уникальных контрагентов: {len(unique_parties)}", "Processing Summary")
|
||||
|
||||
# Вывод список найденных контрагентов с детальной информацией
|
||||
party_list = []
|
||||
for key, data in unique_parties.items():
|
||||
party_list.append(f"{key}: {data['etaxes_party_name']}/{data['etaxes_tax_id']} - {data['erp_party_type']}")
|
||||
|
||||
if party_list:
|
||||
frappe.log_error(f"Список найденных контрагентов:\n" + "\n".join(party_list), "Parties List")
|
||||
else:
|
||||
frappe.log_error("Не найдено ни одного контрагента в инвойсах!", "No Parties Found")
|
||||
|
||||
# Добавляем контрагентов в систему
|
||||
for party_key, party_data in unique_parties.items():
|
||||
|
|
@ -1218,7 +1183,6 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
existing = frappe.db.exists('E-Taxes Parties', {'etaxes_party_name': party_name})
|
||||
|
||||
if existing:
|
||||
frappe.log_error(f"Контрагент уже существует: {party_name} ({party_tin})", "Existing Party")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
|
|
@ -1228,26 +1192,18 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
'doctype': 'E-Taxes Parties',
|
||||
'etaxes_party_name': party_name,
|
||||
'etaxes_tax_id': party_tin,
|
||||
'etaxes_pin': party_data.get('etaxes_pin', ''),
|
||||
'etaxes_address': party_data.get('etaxes_address', ''),
|
||||
'etaxes_party_type': party_data.get('etaxes_party_type', 'Sender'),
|
||||
'erp_party_type': party_data.get('erp_party_type', 'Supplier'),
|
||||
'source_invoice': party_data.get('source_invoice', ''),
|
||||
'status': 'New'
|
||||
})
|
||||
|
||||
frappe.log_error(f"Создание контрагента с данными: {json.dumps(doc.as_dict())}", f"Create Party {party_tin}")
|
||||
|
||||
doc.insert(ignore_permissions=True)
|
||||
|
||||
frappe.log_error(f"Контрагент успешно создан: {party_name} ({party_tin})", "Party Created")
|
||||
created_count += 1
|
||||
except Exception as e:
|
||||
error_msg = f"Ошибка создания контрагента {party_name} ({party_tin}): {str(e)}\n{frappe.get_traceback()}"
|
||||
frappe.log_error(error_msg, "Party Creation Error")
|
||||
failed_count += 1
|
||||
|
||||
frappe.log_error(f"=== ИТОГ ЗАГРУЗКИ КОНТРАГЕНТОВ ===\nСоздано: {created_count}\nПропущено: {skipped_count}\nОшибки: {failed_count}", "Loading Results")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'created_count': created_count,
|
||||
|
|
@ -1258,15 +1214,12 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
|||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"=== КРИТИЧЕСКАЯ ОШИБКА ===\nError in load_parties_from_invoices: {str(e)}\n{frappe.get_traceback()}"
|
||||
frappe.log_error(error_msg, "Critical Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_settings():
|
||||
"""Получение активных настроек E-Taxes"""
|
||||
|
|
@ -1376,22 +1329,34 @@ def get_unmapped_items():
|
|||
# 1. Получаем все товары со статусом 'New'
|
||||
etaxes_items = frappe.get_all('E-Taxes Item',
|
||||
filters={'status': 'New'},
|
||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
|
||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code', 'etaxes_unit', 'etaxes_price'])
|
||||
|
||||
# 2. Проверяем, нет ли их уже в настройках
|
||||
existing_mappings = {}
|
||||
for mapping in settings.item_mappings:
|
||||
existing_mappings[mapping.etaxes_item_name] = True
|
||||
existing_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||
|
||||
# 3. Добавляем только те, которых нет в настройках
|
||||
for item in etaxes_items:
|
||||
# В E-Taxes Item name равен etaxes_item_code из-за настройки autoname
|
||||
if item.name not in existing_mappings:
|
||||
unmapped_items.append({
|
||||
'name': item.name, # Это etaxes_item_code (ID документа)
|
||||
# Создаем объект товара с настройками по умолчанию
|
||||
item_data = {
|
||||
'name': item.name,
|
||||
'etaxes_item_name': item.etaxes_item_name,
|
||||
'etaxes_item_code': item.etaxes_item_code
|
||||
})
|
||||
'etaxes_item_code': item.etaxes_item_code,
|
||||
# Добавляем значения по умолчанию
|
||||
'item_group': settings.default_item_group,
|
||||
'uom': settings.default_uom if not item.etaxes_unit else get_uom_for_unit(item.etaxes_unit),
|
||||
'is_stock_item': settings.is_stock_item,
|
||||
'is_purchase_item': settings.is_purchase_item,
|
||||
'item_tax_template': settings.default_item_tax_template
|
||||
}
|
||||
|
||||
# Добавляем цену, если она есть
|
||||
if hasattr(item, 'etaxes_price') and item.etaxes_price:
|
||||
item_data['standard_rate'] = item.etaxes_price
|
||||
|
||||
unmapped_items.append(item_data)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
|
|
@ -1402,14 +1367,11 @@ def get_unmapped_items():
|
|||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unmapped_parties():
|
||||
"""Получение несопоставленных контрагентов из E-Taxes Parties"""
|
||||
try:
|
||||
# Получаем всех контрагентов из E-Taxes Parties без сопоставления
|
||||
unmapped_parties = []
|
||||
|
||||
# Получаем активные настройки
|
||||
settings = get_active_settings()
|
||||
if not settings:
|
||||
|
|
@ -1418,6 +1380,9 @@ def get_unmapped_parties():
|
|||
'message': 'Не найдены активные настройки E-Taxes'
|
||||
}
|
||||
|
||||
# Получаем всех контрагентов из E-Taxes Parties без сопоставления
|
||||
unmapped_parties = []
|
||||
|
||||
# Получаем список существующих сопоставлений
|
||||
existing_mappings = {}
|
||||
for mapping in settings.party_mappings:
|
||||
|
|
@ -1426,18 +1391,42 @@ def get_unmapped_parties():
|
|||
# Получаем несопоставленные контрагенты из E-Taxes Parties
|
||||
etaxes_parties = frappe.get_all('E-Taxes Parties',
|
||||
filters={'status': 'New'},
|
||||
fields=['etaxes_party_name', 'etaxes_tax_id',
|
||||
'etaxes_party_type', 'erp_party_type'])
|
||||
fields=['name', 'etaxes_party_name', 'etaxes_tax_id',
|
||||
'etaxes_party_type', 'erp_party_type', 'etaxes_address', 'source_invoice'])
|
||||
|
||||
# Фильтруем только те, которых нет в маппингах
|
||||
for party in etaxes_parties:
|
||||
if party.etaxes_party_name not in existing_mappings:
|
||||
unmapped_parties.append({
|
||||
# Определяем тип контрагента (Customer/Supplier)
|
||||
party_type = party.erp_party_type or ('Supplier' if party.etaxes_party_type == 'Sender' else 'Customer')
|
||||
|
||||
# Создаем базовый объект с общими данными
|
||||
party_data = {
|
||||
'name': party.name,
|
||||
'etaxes_party_name': party.etaxes_party_name,
|
||||
'etaxes_tax_id': party.etaxes_tax_id,
|
||||
'etaxes_party_type': party.etaxes_party_type,
|
||||
'erp_party_type': party.erp_party_type
|
||||
})
|
||||
'erp_party_type': party_type,
|
||||
# Общие настройки
|
||||
'payment_terms': settings.default_payment_terms
|
||||
}
|
||||
|
||||
# Добавляем адрес, если он есть
|
||||
if party.etaxes_address:
|
||||
party_data['address'] = party.etaxes_address
|
||||
|
||||
# Добавляем источник, если есть
|
||||
if party.source_invoice:
|
||||
party_data['source'] = party.source_invoice
|
||||
|
||||
# Добавляем специфические поля в зависимости от типа
|
||||
if party_type == 'Supplier':
|
||||
party_data['supplier_group'] = settings.default_supplier_group
|
||||
else: # Customer
|
||||
party_data['customer_group'] = settings.default_customer_group
|
||||
party_data['territory'] = 'All Territories' # Значение по умолчанию
|
||||
|
||||
unmapped_parties.append(party_data)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
|
|
@ -1449,7 +1438,6 @@ def get_unmapped_parties():
|
|||
'message': str(e)
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def match_similar_items():
|
||||
"""Сопоставление товаров по похожим названиям"""
|
||||
|
|
@ -1767,58 +1755,53 @@ def match_similar_parties():
|
|||
|
||||
@frappe.whitelist()
|
||||
def create_unmapped_items(settings_name):
|
||||
"""Создание товаров для всех несопоставленных элементов из E-Taxes"""
|
||||
"""Создание товаров для несопоставленных элементов из табличной части настроек E-Taxes"""
|
||||
try:
|
||||
import re
|
||||
|
||||
# Получаем настройки
|
||||
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
||||
|
||||
# Получаем несопоставленные товары
|
||||
etaxes_items = frappe.get_all('E-Taxes Item',
|
||||
filters={'status': 'New'},
|
||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
|
||||
# Получаем элементы из табличной части, у которых нет сопоставления
|
||||
unmapped_items = []
|
||||
|
||||
# Логируем полученные товары
|
||||
for mapping_item in settings_doc.item_mappings:
|
||||
# Проверяем, что у товара нет сопоставления или оно пустое
|
||||
if not mapping_item.erp_item:
|
||||
# Получаем документ E-Taxes Item для этой записи
|
||||
if frappe.db.exists('E-Taxes Item', mapping_item.etaxes_item_name):
|
||||
etaxes_item = frappe.get_doc('E-Taxes Item', mapping_item.etaxes_item_name)
|
||||
unmapped_items.append(etaxes_item)
|
||||
|
||||
# Получаем существующие маппинги
|
||||
existing_mappings = {}
|
||||
mapping_settings = {} # Для хранения индивидуальных настроек
|
||||
if len(unmapped_items) == 0:
|
||||
return {
|
||||
"success": True,
|
||||
"created_count": 0,
|
||||
"error_count": 0,
|
||||
"message": "Нет несопоставленных товаров в табличной части для создания"
|
||||
}
|
||||
|
||||
created_count = 0
|
||||
error_count = 0
|
||||
|
||||
# Собираем индивидуальные настройки для каждого элемента
|
||||
mapping_settings = {}
|
||||
for mapping in settings_doc.item_mappings:
|
||||
# Используем name (id) документа E-Taxes Item как ключ
|
||||
existing_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||
|
||||
# Сохраняем индивидуальные настройки для этого элемента, если они есть
|
||||
item_settings = {}
|
||||
if mapping.item_group:
|
||||
item_settings['item_group'] = mapping.item_group
|
||||
if mapping.uom:
|
||||
item_settings['uom'] = mapping.uom
|
||||
if mapping.is_stock_item is not None: # Проверяем, что поле не None
|
||||
if mapping.is_stock_item is not None:
|
||||
item_settings['is_stock_item'] = mapping.is_stock_item
|
||||
if mapping.is_purchase_item is not None: # Проверяем, что поле не None
|
||||
if mapping.is_purchase_item is not None:
|
||||
item_settings['is_purchase_item'] = mapping.is_purchase_item
|
||||
if mapping.item_tax_template:
|
||||
item_settings['item_tax_template'] = mapping.item_tax_template
|
||||
|
||||
if item_settings: # Если есть индивидуальные настройки
|
||||
if item_settings:
|
||||
mapping_settings[mapping.etaxes_item_name] = item_settings
|
||||
|
||||
# Логируем существующие маппинги
|
||||
|
||||
# Фильтруем только те, которых нет в маппингах ИЛИ у которых erp_item пустой
|
||||
unmapped_items = []
|
||||
for item in etaxes_items:
|
||||
# Проверяем, что либо товара нет в маппингах, либо соответствующее поле erp_item пустое
|
||||
if item.name not in existing_mappings or not existing_mappings[item.name]:
|
||||
unmapped_items.append(item)
|
||||
|
||||
# Логируем отфильтрованные товары
|
||||
|
||||
created_count = 0
|
||||
error_count = 0
|
||||
|
||||
for etaxes_item in unmapped_items:
|
||||
try:
|
||||
# Проверяем, существует ли уже товар с таким именем
|
||||
|
|
@ -1836,7 +1819,9 @@ def create_unmapped_items(settings_name):
|
|||
if not item_code or len(item_code) < 3:
|
||||
item_code = "ITEM-" + frappe.generate_hash(length=8)
|
||||
|
||||
# Логируем создаваемый товар
|
||||
# Проверяем, что item_code уникален
|
||||
if frappe.db.exists('Item', {'item_code': item_code}):
|
||||
item_code = item_code + "-" + frappe.generate_hash(length=5)
|
||||
|
||||
item_doc.item_code = item_code
|
||||
item_doc.item_name = etaxes_item.etaxes_item_name
|
||||
|
|
@ -1846,8 +1831,19 @@ def create_unmapped_items(settings_name):
|
|||
individual_settings = mapping_settings.get(etaxes_item.name, {})
|
||||
|
||||
# Применяем настройки с учетом индивидуальных приоритетов
|
||||
item_doc.item_group = individual_settings.get('item_group', settings_doc.default_item_group)
|
||||
item_doc.stock_uom = individual_settings.get('uom', settings_doc.default_uom)
|
||||
item_group = individual_settings.get('item_group', settings_doc.default_item_group)
|
||||
stock_uom = individual_settings.get('uom', settings_doc.default_uom)
|
||||
|
||||
# Проверяем существование item_group
|
||||
if not frappe.db.exists('Item Group', item_group):
|
||||
item_group = "All Item Groups"
|
||||
|
||||
# Проверяем существование UOM
|
||||
if not frappe.db.exists('UOM', stock_uom):
|
||||
stock_uom = "Nos"
|
||||
|
||||
item_doc.item_group = item_group
|
||||
item_doc.stock_uom = stock_uom
|
||||
|
||||
# Для булевых полей проверяем, что они не None
|
||||
if 'is_stock_item' in individual_settings:
|
||||
|
|
@ -1860,39 +1856,32 @@ def create_unmapped_items(settings_name):
|
|||
else:
|
||||
item_doc.is_purchase_item = getattr(settings_doc, 'is_purchase_item', 1)
|
||||
|
||||
# Если есть шаблон налогов, применяем его
|
||||
# Правильно добавляем налоговый шаблон
|
||||
if 'item_tax_template' in individual_settings:
|
||||
item_doc.taxes = [{
|
||||
"item_tax_template": individual_settings['item_tax_template']
|
||||
}]
|
||||
tax_template = individual_settings['item_tax_template']
|
||||
if frappe.db.exists('Item Tax Template', tax_template):
|
||||
tax_row = item_doc.append('taxes', {})
|
||||
tax_row.item_tax_template = tax_template
|
||||
elif hasattr(settings_doc, 'default_item_tax_template') and settings_doc.default_item_tax_template:
|
||||
item_doc.taxes = [{
|
||||
"item_tax_template": settings_doc.default_item_tax_template
|
||||
}]
|
||||
tax_template = settings_doc.default_item_tax_template
|
||||
if frappe.db.exists('Item Tax Template', tax_template):
|
||||
tax_row = item_doc.append('taxes', {})
|
||||
tax_row.item_tax_template = tax_template
|
||||
|
||||
# Сохраняем товар
|
||||
item_doc.insert()
|
||||
|
||||
# Логируем созданный товар
|
||||
|
||||
# Проверяем, существует ли уже маппинг для этого товара
|
||||
existing_row = None
|
||||
for idx, mapping in enumerate(settings_doc.item_mappings):
|
||||
# Обновляем маппинг в настройках
|
||||
for mapping in settings_doc.item_mappings:
|
||||
if mapping.etaxes_item_name == etaxes_item.name:
|
||||
existing_row = idx
|
||||
mapping.erp_item = item_doc.name
|
||||
mapping.mapping_type = 'Automatic'
|
||||
break
|
||||
|
||||
if existing_row is not None:
|
||||
# Если строка уже существует, обновляем её значение erp_item
|
||||
settings_doc.item_mappings[existing_row].erp_item = item_doc.name
|
||||
settings_doc.item_mappings[existing_row].mapping_type = 'Automatic'
|
||||
else:
|
||||
# Если строки нет, добавляем новую
|
||||
settings_doc.append('item_mappings', {
|
||||
'etaxes_item_name': etaxes_item.name, # Используем ID документа
|
||||
'erp_item': item_doc.name,
|
||||
'mapping_type': 'Automatic'
|
||||
})
|
||||
# Обновляем статус E-Taxes Item
|
||||
etaxes_item.status = 'Mapped'
|
||||
etaxes_item.mapped_item = item_doc.name
|
||||
etaxes_item.save()
|
||||
|
||||
created_count += 1
|
||||
|
||||
|
|
@ -1902,18 +1891,6 @@ def create_unmapped_items(settings_name):
|
|||
# Сохраняем настройки после добавления всех сопоставлений
|
||||
if created_count > 0:
|
||||
settings_doc.save()
|
||||
|
||||
# Обновляем статусы E-Taxes Item
|
||||
for etaxes_item in unmapped_items:
|
||||
# Ищем сопоставление по ID документа
|
||||
for mapping in settings_doc.item_mappings:
|
||||
if mapping.etaxes_item_name == etaxes_item.name and mapping.erp_item:
|
||||
item_doc = frappe.get_doc('E-Taxes Item', etaxes_item.name)
|
||||
item_doc.status = 'Mapped'
|
||||
item_doc.mapped_item = mapping.erp_item
|
||||
item_doc.save()
|
||||
break
|
||||
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
|
|
@ -1927,52 +1904,7 @@ def create_unmapped_items(settings_name):
|
|||
"success": False,
|
||||
"message": str(e)
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unmapped_items():
|
||||
"""Получение несопоставленных товаров из E-Taxes"""
|
||||
try:
|
||||
# Получаем активные настройки
|
||||
settings = get_active_settings()
|
||||
if not settings:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Не найдены активные настройки E-Taxes'
|
||||
}
|
||||
|
||||
# Получаем все товары из E-Taxes без сопоставления
|
||||
unmapped_items = []
|
||||
|
||||
# 1. Получаем все товары со статусом 'New'
|
||||
etaxes_items = frappe.get_all('E-Taxes Item',
|
||||
filters={'status': 'New'},
|
||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
|
||||
|
||||
# 2. Проверяем, нет ли их уже в настройках
|
||||
existing_mappings = {}
|
||||
for mapping in settings.item_mappings:
|
||||
existing_mappings[mapping.etaxes_item_name] = True
|
||||
|
||||
# 3. Добавляем только те, которых нет в настройках
|
||||
# КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: проверяем по etaxes_item_name, а не по item.name
|
||||
for item in etaxes_items:
|
||||
if item.etaxes_item_name not in existing_mappings:
|
||||
unmapped_items.append({
|
||||
'name': item.name, # ID документа (равен etaxes_item_code из-за autoname)
|
||||
'etaxes_item_name': item.etaxes_item_name,
|
||||
'etaxes_item_code': item.etaxes_item_code
|
||||
})
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'items': unmapped_items
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule_date=None, warehouse=None):
|
||||
"""Импорт инвойса с учетом сопоставлений и обработкой несопоставленных элементов"""
|
||||
|
|
@ -2360,31 +2292,44 @@ def debug_azeri_matching(search_term, compare_with):
|
|||
|
||||
@frappe.whitelist()
|
||||
def create_unmapped_parties(settings_name):
|
||||
"""Создание контрагентов для всех несопоставленных элементов из E-Taxes Parties"""
|
||||
"""Создание контрагентов для несопоставленных элементов из табличной части настроек E-Taxes"""
|
||||
try:
|
||||
import re
|
||||
|
||||
# Получаем настройки
|
||||
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
||||
|
||||
# Получаем несопоставленные контрагенты
|
||||
etaxes_parties = frappe.get_all('E-Taxes Parties',
|
||||
filters={'status': 'New'},
|
||||
fields=['name', 'etaxes_party_name', 'etaxes_tax_id',
|
||||
'etaxes_party_type', 'erp_party_type'])
|
||||
# Получаем элементы из табличной части, у которых нет сопоставления
|
||||
unmapped_parties = []
|
||||
|
||||
# Логируем полученные контрагенты
|
||||
frappe.log_error(f"E-Taxes parties with status New: {etaxes_parties}", "Create Parties Debug")
|
||||
for mapping_party in settings_doc.party_mappings:
|
||||
# Проверяем, что у контрагента нет сопоставления или оно пустое
|
||||
if not mapping_party.erp_party:
|
||||
# Получаем документ E-Taxes Parties для этой записи
|
||||
parties = frappe.get_all('E-Taxes Parties',
|
||||
filters={
|
||||
'etaxes_party_name': mapping_party.etaxes_party_name,
|
||||
'etaxes_tax_id': mapping_party.etaxes_tax_id
|
||||
},
|
||||
fields=['name'])
|
||||
if parties:
|
||||
etaxes_party = frappe.get_doc('E-Taxes Parties', parties[0].name)
|
||||
# Добавляем тип контрагента из настроек
|
||||
etaxes_party.erp_party_type = mapping_party.party_type
|
||||
unmapped_parties.append(etaxes_party)
|
||||
|
||||
# Получаем существующие маппинги
|
||||
existing_mappings = {}
|
||||
mapping_settings = {} # Для хранения индивидуальных настроек
|
||||
if len(unmapped_parties) == 0:
|
||||
return {
|
||||
"success": True,
|
||||
"created_count": 0,
|
||||
"error_count": 0,
|
||||
"message": "Нет несопоставленных контрагентов в табличной части для создания"
|
||||
}
|
||||
|
||||
created_count = 0
|
||||
error_count = 0
|
||||
|
||||
# Собираем индивидуальные настройки для каждого элемента
|
||||
mapping_settings = {}
|
||||
for mapping in settings_doc.party_mappings:
|
||||
# Используем name (id) документа E-Taxes Parties как ключ
|
||||
existing_mappings[mapping.etaxes_party_name] = mapping.erp_party
|
||||
|
||||
# Сохраняем индивидуальные настройки для этого элемента, если они есть
|
||||
party_settings = {}
|
||||
if mapping.supplier_group:
|
||||
party_settings['supplier_group'] = mapping.supplier_group
|
||||
|
|
@ -2395,54 +2340,39 @@ def create_unmapped_parties(settings_name):
|
|||
if mapping.payment_terms:
|
||||
party_settings['payment_terms'] = mapping.payment_terms
|
||||
|
||||
if party_settings: # Если есть индивидуальные настройки
|
||||
mapping_settings[mapping.etaxes_party_name] = party_settings
|
||||
|
||||
# Логируем существующие маппинги
|
||||
frappe.log_error(f"Existing mappings: {existing_mappings}", "Create Parties Debug")
|
||||
|
||||
# Фильтруем только те, которых нет в маппингах ИЛИ у которых erp_party пустой
|
||||
unmapped_parties = []
|
||||
for party in etaxes_parties:
|
||||
# Проверяем, что либо контрагента нет в маппингах, либо соответствующее поле erp_party пустое
|
||||
if party.etaxes_party_name not in existing_mappings or not existing_mappings[party.etaxes_party_name]:
|
||||
unmapped_parties.append(party)
|
||||
|
||||
# Логируем отфильтрованных контрагентов
|
||||
frappe.log_error(f"Unmapped parties to create: {unmapped_parties}", "Create Parties Debug")
|
||||
|
||||
created_count = 0
|
||||
error_count = 0
|
||||
if party_settings:
|
||||
key = f"{mapping.etaxes_party_name}|{mapping.etaxes_tax_id}"
|
||||
mapping_settings[key] = party_settings
|
||||
|
||||
for etaxes_party in unmapped_parties:
|
||||
try:
|
||||
# Определяем тип контрагента (Customer/Supplier) на основе erp_party_type
|
||||
# Определяем тип контрагента (Customer/Supplier)
|
||||
party_type = etaxes_party.erp_party_type
|
||||
|
||||
# Получаем индивидуальные настройки для этого контрагента
|
||||
individual_settings = mapping_settings.get(etaxes_party.etaxes_party_name, {})
|
||||
|
||||
# Проверяем, существует ли уже контрагент с таким названием и типом
|
||||
party_field = 'supplier_name' if party_type == 'Supplier' else 'customer_name'
|
||||
party_exists = frappe.db.exists(party_type, {party_field: etaxes_party.etaxes_party_name})
|
||||
|
||||
if party_exists:
|
||||
frappe.log_error(f"{party_type} already exists: {etaxes_party.etaxes_party_name}", "Create Parties Debug")
|
||||
continue
|
||||
|
||||
# Логируем создаваемого контрагента
|
||||
frappe.log_error(f"Creating {party_type}: {etaxes_party.etaxes_party_name}", "Create Parties Debug")
|
||||
|
||||
# Создаем новый контрагент
|
||||
# Создаем нового контрагента
|
||||
party_doc = frappe.new_doc(party_type)
|
||||
|
||||
# Получаем индивидуальные настройки
|
||||
key = f"{etaxes_party.etaxes_party_name}|{etaxes_party.etaxes_tax_id}"
|
||||
individual_settings = mapping_settings.get(key, {})
|
||||
|
||||
# Задаем основные поля
|
||||
if party_type == "Supplier":
|
||||
party_doc.supplier_name = etaxes_party.etaxes_party_name
|
||||
party_doc.supplier_type = "Company" # Можно настроить при необходимости
|
||||
|
||||
# Применяем настройки с учетом индивидуальных приоритетов
|
||||
party_doc.supplier_group = individual_settings.get('supplier_group', settings_doc.default_supplier_group)
|
||||
supplier_group = individual_settings.get('supplier_group', settings_doc.default_supplier_group)
|
||||
if not frappe.db.exists('Supplier Group', supplier_group):
|
||||
supplier_group = "All Supplier Groups"
|
||||
party_doc.supplier_group = supplier_group
|
||||
|
||||
# Если указан tax_id, добавляем его
|
||||
if etaxes_party.etaxes_tax_id:
|
||||
|
|
@ -2458,14 +2388,16 @@ def create_unmapped_parties(settings_name):
|
|||
party_doc.customer_type = "Company" # Можно настроить при необходимости
|
||||
|
||||
# Применяем настройки с учетом индивидуальных приоритетов
|
||||
party_doc.customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group)
|
||||
customer_group = individual_settings.get('customer_group', settings_doc.default_customer_group)
|
||||
if not frappe.db.exists('Customer Group', customer_group):
|
||||
customer_group = "All Customer Groups"
|
||||
party_doc.customer_group = customer_group
|
||||
|
||||
# Территория
|
||||
if 'territory' in individual_settings:
|
||||
party_doc.territory = individual_settings['territory']
|
||||
else:
|
||||
# Можно использовать "All Territories" как дефолтное значение
|
||||
party_doc.territory = "All Territories"
|
||||
territory = individual_settings.get('territory', "All Territories")
|
||||
if not frappe.db.exists('Territory', territory):
|
||||
territory = "All Territories"
|
||||
party_doc.territory = territory
|
||||
|
||||
# Если указан tax_id, добавляем его
|
||||
if etaxes_party.etaxes_tax_id:
|
||||
|
|
@ -2480,49 +2412,28 @@ def create_unmapped_parties(settings_name):
|
|||
# Сохраняем контрагента
|
||||
party_doc.insert()
|
||||
|
||||
# Логируем созданного контрагента
|
||||
frappe.log_error(f"{party_type} created successfully: {party_doc.name}", "Create Parties Debug")
|
||||
|
||||
# Проверяем, существует ли уже маппинг для этого контрагента
|
||||
existing_row = None
|
||||
for idx, mapping in enumerate(settings_doc.party_mappings):
|
||||
if mapping.etaxes_party_name == etaxes_party.etaxes_party_name:
|
||||
existing_row = idx
|
||||
# Обновляем маппинг в настройках
|
||||
for mapping in settings_doc.party_mappings:
|
||||
if mapping.etaxes_party_name == etaxes_party.etaxes_party_name and mapping.etaxes_tax_id == etaxes_party.etaxes_tax_id:
|
||||
mapping.erp_party = party_doc.name
|
||||
mapping.party_type = party_type
|
||||
mapping.mapping_type = 'Automatic'
|
||||
break
|
||||
|
||||
if existing_row is not None:
|
||||
# Если строка уже существует, обновляем её значение erp_party
|
||||
settings_doc.party_mappings[existing_row].erp_party = party_doc.name
|
||||
settings_doc.party_mappings[existing_row].party_type = party_type
|
||||
settings_doc.party_mappings[existing_row].mapping_type = 'Automatic'
|
||||
else:
|
||||
# Если строки нет, добавляем новую
|
||||
settings_doc.append('party_mappings', {
|
||||
'etaxes_party_name': etaxes_party.etaxes_party_name,
|
||||
'etaxes_tax_id': etaxes_party.etaxes_tax_id,
|
||||
'party_type': party_type,
|
||||
'erp_party': party_doc.name,
|
||||
'mapping_type': 'Automatic'
|
||||
})
|
||||
|
||||
# Обновляем запись в E-Taxes Parties
|
||||
party_doc_etaxes = frappe.get_doc('E-Taxes Parties', etaxes_party.name)
|
||||
party_doc_etaxes.status = 'Mapped'
|
||||
party_doc_etaxes.mapped_party = party_doc.name
|
||||
party_doc_etaxes.save()
|
||||
# Обновляем статус E-Taxes Parties
|
||||
etaxes_party.status = 'Mapped'
|
||||
etaxes_party.mapped_party = party_doc.name
|
||||
etaxes_party.save()
|
||||
|
||||
created_count += 1
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error creating party {etaxes_party.etaxes_party_name}: {str(e)}", "Create Parties Debug")
|
||||
error_count += 1
|
||||
|
||||
# Сохраняем настройки после добавления всех сопоставлений
|
||||
if created_count > 0:
|
||||
settings_doc.save()
|
||||
|
||||
frappe.log_error(f"Final result: created {created_count} parties, errors: {error_count}", "Create Parties Debug")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"created_count": created_count,
|
||||
|
|
@ -2531,12 +2442,11 @@ def create_unmapped_parties(settings_name):
|
|||
}
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in create_unmapped_parties: {str(e)}\n{frappe.get_traceback()}", "Create Parties Debug")
|
||||
return {
|
||||
"success": False,
|
||||
"message": str(e)
|
||||
}
|
||||
|
||||
|
||||
def handle_unauthorized_request(asan_login_name, operation_name="API request"):
|
||||
"""Общая функция для обработки неавторизованных запросов"""
|
||||
# Логирование события
|
||||
|
|
@ -2584,4 +2494,145 @@ def check_token_validity(asan_login_name=None):
|
|||
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in check_token_validity: {str(e)}\n{frappe.get_traceback()}", "Token Validity Check")
|
||||
return {"valid": False, "message": f"Error: {str(e)}"}
|
||||
return {"valid": False, "message": f"Error: {str(e)}"}
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_invoice_exists(serial_number):
|
||||
"""Проверяет, существует ли уже инвойс с указанным серийным номером"""
|
||||
try:
|
||||
# Проверяем наличие заказа с указанным серийным номером
|
||||
exists = frappe.db.exists('Purchase Order', {'invoice_serial_number': serial_number, 'docstatus': ['!=', 2]})
|
||||
|
||||
return {
|
||||
'exists': bool(exists),
|
||||
'serial_number': serial_number
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in check_invoice_exists: {str(e)}", "API Error")
|
||||
return {
|
||||
'exists': False,
|
||||
'error': str(e)
|
||||
}
|
||||
@frappe.whitelist()
|
||||
def get_etaxes_purchases():
|
||||
"""Получает список всех записей E-Taxes Purchase"""
|
||||
try:
|
||||
purchases = frappe.get_all('E-Taxes Purchase',
|
||||
fields=['name', 'etaxes_id', 'date', 'party', 'total'])
|
||||
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"Retrieved {len(purchases)} E-Taxes Purchase records")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'purchases': purchases
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in get_etaxes_purchases: {str(e)}", "API Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_etaxes_purchase(etaxes_id, date, party, total):
|
||||
"""Создает запись E-Taxes Purchase для отслеживания импортированных инвойсов"""
|
||||
try:
|
||||
# Нормализуем ID для хранения
|
||||
etaxes_id = str(etaxes_id).strip() if etaxes_id else ""
|
||||
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"Creating E-Taxes Purchase: ID={etaxes_id}, date={date}, party={party}, total={total}")
|
||||
|
||||
# Проверяем, существует ли уже запись с таким etaxes_id
|
||||
existing = frappe.db.get_value('E-Taxes Purchase', {'etaxes_id': etaxes_id}, 'name')
|
||||
if existing:
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"E-Taxes Purchase already exists: {existing}")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Record already exists',
|
||||
'name': existing
|
||||
}
|
||||
|
||||
# Создаем новую запись
|
||||
etaxes_purchase = frappe.new_doc('E-Taxes Purchase')
|
||||
etaxes_purchase.etaxes_id = etaxes_id
|
||||
etaxes_purchase.date = date
|
||||
etaxes_purchase.party = party
|
||||
etaxes_purchase.total = total
|
||||
|
||||
etaxes_purchase.insert()
|
||||
frappe.db.commit() # Явный commit
|
||||
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"Created new E-Taxes Purchase: {etaxes_purchase.name}")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'name': etaxes_purchase.name
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in create_etaxes_purchase: {str(e)}\n{frappe.get_traceback()}", "API Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def link_purchase_order_to_etaxes(purchase_order, etaxes_purchase):
|
||||
"""Устанавливает связь между Purchase Order и E-Taxes Purchase"""
|
||||
try:
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"Linking Purchase Order {purchase_order} to E-Taxes Purchase {etaxes_purchase}")
|
||||
|
||||
# Проверяем, существуют ли оба документа
|
||||
po_exists = frappe.db.exists('Purchase Order', purchase_order)
|
||||
etaxes_exists = frappe.db.exists('E-Taxes Purchase', etaxes_purchase)
|
||||
|
||||
if not po_exists or not etaxes_exists:
|
||||
frappe.log_error(f"Link error: PO exists={po_exists}, E-Taxes exists={etaxes_exists}", "Link Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'One or both documents do not exist'
|
||||
}
|
||||
|
||||
# Получаем документ Purchase Order
|
||||
po_doc = frappe.get_doc('Purchase Order', purchase_order)
|
||||
|
||||
# Устанавливаем связь с E-Taxes Purchase
|
||||
po_doc.is_taxes_doc = 1
|
||||
po_doc.taxes_doc = etaxes_purchase
|
||||
|
||||
# Сохраняем изменения
|
||||
po_doc.save()
|
||||
frappe.db.commit() # Явный commit
|
||||
|
||||
# Логирование для отладки
|
||||
frappe.logger().info(f"Successfully linked PO and E-Taxes Purchase")
|
||||
|
||||
return {
|
||||
'success': True
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error in link_purchase_order_to_etaxes: {str(e)}\n{frappe.get_traceback()}", "API Error")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
def on_delete_purchase_order(doc, method):
|
||||
"""Удаляет связанную запись E-Taxes Purchase при удалении Purchase Order"""
|
||||
if doc.is_taxes_doc and doc.taxes_doc:
|
||||
try:
|
||||
frappe.logger().info(f"Deleting E-Taxes Purchase {doc.taxes_doc} due to Purchase Order {doc.name} deletion")
|
||||
|
||||
# Проверяем, существует ли запись E-Taxes Purchase
|
||||
if frappe.db.exists("E-Taxes Purchase", doc.taxes_doc):
|
||||
frappe.delete_doc('E-Taxes Purchase', doc.taxes_doc, ignore_permissions=True, force=True)
|
||||
frappe.db.commit()
|
||||
frappe.logger().info(f"Successfully deleted E-Taxes Purchase {doc.taxes_doc}")
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error deleting E-Taxes Purchase {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}",
|
||||
"Purchase Order Delete Error")
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
|
||||
frappe.ui.form.on('Purchase Order', {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.add_custom_button(__('Импорт из E-Taxes'), function() {
|
||||
show_etaxes_import_dialog(frm);
|
||||
}, __('Создать'));
|
||||
// Проверяем, установлена ли галочка is_taxes_doc
|
||||
if (frm.doc.is_taxes_doc) {
|
||||
// Делаем все поля read-only
|
||||
frm.set_read_only(true);
|
||||
|
||||
// Альтернативно, можно добавить визуальное предупреждение
|
||||
frm.page.set_indicator(__('Налоговый документ'), 'blue');
|
||||
}
|
||||
},
|
||||
|
||||
// Делаем поле is_taxes_doc тоже read-only при открытии документа
|
||||
onload: function(frm) {
|
||||
frm.set_df_property('is_taxes_doc', 'read_only', 1);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.listview_settings['Purchase Order'] = {
|
||||
// Добавляем необходимые поля в запрос
|
||||
add_fields: ['supplier', 'transaction_date', 'status', 'docstatus'],
|
||||
add_fields: ['supplier', 'transaction_date', 'status', 'docstatus', 'is_taxes_doc', 'taxes_doc'],
|
||||
|
||||
// Настраиваем индикаторы статусов
|
||||
get_indicator: function(doc) {
|
||||
|
|
@ -42,8 +51,11 @@ frappe.listview_settings['Purchase Order'] = {
|
|||
// Глобальная переменная для хранения диалога загрузки
|
||||
var loading_dialog = null;
|
||||
|
||||
// Функция для отображения диалога загрузки
|
||||
function show_loading_dialog(message) {
|
||||
// Глобальная переменная для отмены процесса загрузки
|
||||
var cancel_loading = false;
|
||||
|
||||
// Функция для отображения диалога загрузки с прогресс-баром
|
||||
function show_loading_dialog(message, show_progress = false) {
|
||||
if (!loading_dialog) {
|
||||
loading_dialog = new frappe.ui.Dialog({
|
||||
title: __('Загрузка'),
|
||||
|
|
@ -53,7 +65,7 @@ function show_loading_dialog(message) {
|
|||
fieldtype: 'HTML',
|
||||
options: `
|
||||
<div class="text-center">
|
||||
<div class="lds-dual-ring" style="display: inline-block; width: 64px; height: 64px;">
|
||||
<div class="loading-animation" style="display: inline-block; width: 64px; height: 64px;">
|
||||
<div style="width: 64px; height: 64px; border: 6px solid #4e73df; border-radius: 50%; border-color: #4e73df transparent #4e73df transparent; animation: lds-dual-ring 1.2s linear infinite;"></div>
|
||||
</div>
|
||||
<style>
|
||||
|
|
@ -61,6 +73,31 @@ function show_loading_dialog(message) {
|
|||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
display: none;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 4px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background-color: #4e73df;
|
||||
border-radius: 4px;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
<div class="mt-3" id="status_message">
|
||||
<h4>${message || __('Пожалуйста, подождите...')}</h4>
|
||||
|
|
@ -73,29 +110,79 @@ function show_loading_dialog(message) {
|
|||
</div>
|
||||
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
||||
</div>
|
||||
<!-- Прогресс-бар -->
|
||||
<div class="progress-container" id="progress_container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-fill" id="progress_bar_fill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progress_text">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Отмена'),
|
||||
primary_action: function() {
|
||||
hide_loading_dialog();
|
||||
cancel_loading = true;
|
||||
loading_dialog.$wrapper.find('.primary-action').prop('disabled', true);
|
||||
loading_dialog.$wrapper.find('.primary-action').html(__('Отмена в процессе...'));
|
||||
$('#status_message p').text(__('Отмена загрузки. Уже загруженные инвойсы сохранятся.'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Обновляем сообщение
|
||||
$('#status_message h4').text(message || __('Пожалуйста, подождите...'));
|
||||
$('#status_message p').text(__('Операция может занять некоторое время'));
|
||||
}
|
||||
|
||||
// Показываем или скрываем прогресс-бар
|
||||
if (show_progress) {
|
||||
$('#progress_container').show();
|
||||
// Сбрасываем прогресс
|
||||
$('#progress_bar_fill').css('width', '0%');
|
||||
$('#progress_text').text('0%');
|
||||
} else {
|
||||
$('#progress_container').hide();
|
||||
}
|
||||
|
||||
// Сбрасываем флаг отмены
|
||||
cancel_loading = false;
|
||||
|
||||
loading_dialog.show();
|
||||
}
|
||||
|
||||
// Функция для обновления прогресса
|
||||
function update_progress(current, total) {
|
||||
if (loading_dialog) {
|
||||
var percent = Math.round((current / total) * 100);
|
||||
$('#progress_bar_fill').css('width', percent + '%');
|
||||
$('#progress_text').text(current + ' из ' + total + ' (' + percent + '%)');
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для скрытия диалога загрузки
|
||||
function hide_loading_dialog() {
|
||||
if (loading_dialog) {
|
||||
loading_dialog.hide();
|
||||
try {
|
||||
loading_dialog.hide();
|
||||
} catch (e) {
|
||||
console.error("Error hiding loading dialog:", e);
|
||||
}
|
||||
loading_dialog = null;
|
||||
}
|
||||
// Сбрасываем флаг отмены
|
||||
cancel_loading = false;
|
||||
}
|
||||
|
||||
function ensure_loading_dialog_closed(timeout = 10000) {
|
||||
// Устанавливаем таймаут для закрытия диалога
|
||||
if (loading_dialog) {
|
||||
setTimeout(function() {
|
||||
if (loading_dialog) {
|
||||
hide_loading_dialog();
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для обновления сообщения в диалоге загрузки
|
||||
|
|
@ -121,7 +208,7 @@ function show_verification_code(code) {
|
|||
function set_loading_success(message, submessage, callback, delay = 2000) {
|
||||
if (loading_dialog) {
|
||||
// Меняем анимацию на галочку
|
||||
loading_dialog.$wrapper.find('.lds-dual-ring').html(`
|
||||
loading_dialog.$wrapper.find('.loading-animation').html(`
|
||||
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
|
||||
</div>
|
||||
|
|
@ -133,8 +220,9 @@ function set_loading_success(message, submessage, callback, delay = 2000) {
|
|||
$('#status_message p').text(submessage);
|
||||
}
|
||||
|
||||
// Скрываем код верификации
|
||||
// Скрываем код верификации и прогресс
|
||||
$('#verification_code_container').hide();
|
||||
$('#progress_container').hide();
|
||||
|
||||
// Удаляем кнопки
|
||||
loading_dialog.set_primary_action(null);
|
||||
|
|
@ -157,7 +245,7 @@ function set_loading_success(message, submessage, callback, delay = 2000) {
|
|||
function set_loading_error(message, submessage, show_close_button = true) {
|
||||
if (loading_dialog) {
|
||||
// Меняем анимацию на крестик
|
||||
loading_dialog.$wrapper.find('.lds-dual-ring').html(`
|
||||
loading_dialog.$wrapper.find('.loading-animation').html(`
|
||||
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fa fa-times" style="font-size: 48px; color: #d9534f;"></i>
|
||||
</div>
|
||||
|
|
@ -169,8 +257,9 @@ function set_loading_error(message, submessage, show_close_button = true) {
|
|||
$('#status_message p').text(submessage);
|
||||
}
|
||||
|
||||
// Скрываем код верификации
|
||||
// Скрываем код верификации и прогресс
|
||||
$('#verification_code_container').hide();
|
||||
$('#progress_container').hide();
|
||||
|
||||
// При необходимости добавляем кнопку закрытия
|
||||
if (show_close_button) {
|
||||
|
|
@ -353,8 +442,7 @@ function poll_authentication_status(asan_login_name, bearer_token, callback) {
|
|||
},
|
||||
error: function(err) {
|
||||
// В случае ошибки продолжаем опрос
|
||||
console.error('Error during status check:', err);
|
||||
setTimeout(pollStatus, 6000);
|
||||
setTimeout(pollStatus, 6000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -440,8 +528,7 @@ function complete_authentication(asan_login_name, callback) {
|
|||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error parsing certificate:', e);
|
||||
hide_loading_dialog();
|
||||
hide_loading_dialog();
|
||||
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -651,94 +738,6 @@ function show_etaxes_import_dialog(frm) {
|
|||
});
|
||||
}
|
||||
|
||||
// Новая функция для загрузки инвойсов с обработкой ошибок авторизации
|
||||
function load_etaxes_invoices(frm, fromDate, toDate, warehouse) {
|
||||
// Сначала получаем настройки авторизации
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.get_default_asan_login',
|
||||
callback: function(login_response) {
|
||||
if (login_response.message && login_response.message.found) {
|
||||
// Получаем токен из настроек
|
||||
let main_token = login_response.message.main_token;
|
||||
|
||||
if (!main_token) {
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка авторизации'),
|
||||
indicator: 'red',
|
||||
message: __('Не найден токен авторизации. Пожалуйста, завершите процесс авторизации в настройках E-Taxes.')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем счета-фактуры с использованием полученного токена
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.get_invoices',
|
||||
args: {
|
||||
'token': main_token,
|
||||
'filters': JSON.stringify({
|
||||
"creationDateFrom": fromDate,
|
||||
"creationDateTo": toDate
|
||||
})
|
||||
},
|
||||
callback: function(r) {
|
||||
// Закрываем диалог загрузки
|
||||
hide_loading_dialog();
|
||||
|
||||
if (r.message && !r.message.error) {
|
||||
// Получаем данные о счетах-фактурах
|
||||
let invoices = r.message.data || r.message.invoices || [];
|
||||
|
||||
if (invoices.length > 0) {
|
||||
// Передаем токен, frm и warehouse в следующий диалог
|
||||
show_invoice_selection_dialog(frm, invoices, main_token, warehouse);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Информация'),
|
||||
indicator: 'blue',
|
||||
message: __('За указанный период не найдено счетов-фактур')
|
||||
});
|
||||
}
|
||||
} else if (r.message && r.message.error === 'unauthorized') {
|
||||
// Если ошибка авторизации, предлагаем авторизоваться
|
||||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
// Запускаем процесс аутентификации и после успеха повторяем загрузку
|
||||
start_authentication_process(function() {
|
||||
// После успешной аутентификации повторяем загрузку
|
||||
load_etaxes_invoices(frm, fromDate, toDate, warehouse);
|
||||
});
|
||||
},
|
||||
function() {
|
||||
// Если пользователь отказался, показываем сообщение
|
||||
frappe.show_alert({
|
||||
message: __('Authentication canceled. Operation cannot be completed.'),
|
||||
indicator: 'red'
|
||||
}, 5);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: r.message ? r.message.message : __('Ошибка загрузки счетов-фактур')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: __('Не найдены настройки авторизации E-Taxes')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для отображения диалога импорта при работе из списка
|
||||
function show_etaxes_import_dialog_for_list() {
|
||||
// Сначала проверяем токен
|
||||
|
|
@ -816,6 +815,215 @@ function show_etaxes_import_dialog_for_list() {
|
|||
});
|
||||
}
|
||||
|
||||
// Функция для загрузки инвойсов с фильтрацией дубликатов
|
||||
function load_etaxes_invoices(frm, fromDate, toDate, warehouse) {
|
||||
// Устанавливаем таймаут для автоматического закрытия диалога
|
||||
ensure_loading_dialog_closed(30000); // 30 секунд максимум
|
||||
|
||||
// Сначала получаем настройки авторизации
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.get_default_asan_login',
|
||||
callback: function(login_response) {
|
||||
if (!loading_dialog) {
|
||||
// Если диалог был закрыт (пользователь ушел со страницы), прекращаем операцию
|
||||
return;
|
||||
}
|
||||
|
||||
if (login_response.message && login_response.message.found) {
|
||||
// Получаем токен из настроек
|
||||
let main_token = login_response.message.main_token;
|
||||
|
||||
if (!main_token) {
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка авторизации'),
|
||||
indicator: 'red',
|
||||
message: __('Не найден токен авторизации. Пожалуйста, завершите процесс авторизации в настройках E-Taxes.')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем счета-фактуры с использованием полученного токена
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.get_invoices',
|
||||
args: {
|
||||
'token': main_token,
|
||||
'filters': JSON.stringify({
|
||||
"creationDateFrom": fromDate,
|
||||
"creationDateTo": toDate
|
||||
})
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!loading_dialog) {
|
||||
// Если диалог был закрыт, прекращаем операцию
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.message && !r.message.error) {
|
||||
// Получаем данные о счетах-фактурах
|
||||
let invoices = r.message.data || r.message.invoices || [];
|
||||
|
||||
if (invoices.length > 0) {
|
||||
// Показываем диалог загрузки для проверки дубликатов
|
||||
update_loading_message(
|
||||
__('Проверка на дубликаты...'),
|
||||
__('Фильтрация уже импортированных счетов-фактур')
|
||||
);
|
||||
|
||||
// Получаем список уже импортированных инвойсов из E-Taxes Purchase
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.get_etaxes_purchases',
|
||||
callback: function(etaxes_r) {
|
||||
// Проверяем, что диалог все еще открыт
|
||||
if (!loading_dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Преобразуем данные E-Taxes Purchase в хеш-таблицу для быстрого поиска
|
||||
let importedInvoices = {};
|
||||
let importedCount = 0;
|
||||
|
||||
if (etaxes_r.message && etaxes_r.message.success && etaxes_r.message.purchases) {
|
||||
importedCount = etaxes_r.message.purchases.length;
|
||||
|
||||
etaxes_r.message.purchases.forEach(function(purchase) {
|
||||
// Проверяем и нормализуем ID
|
||||
if (purchase.etaxes_id) {
|
||||
// Нормализуем ID для более надежного сравнения
|
||||
let normId = String(purchase.etaxes_id).trim();
|
||||
importedInvoices[normId] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Фильтруем список инвойсов, исключая дубликаты
|
||||
let filteredInvoices = [];
|
||||
let skippedCount = 0;
|
||||
|
||||
for (let i = 0; i < invoices.length; i++) {
|
||||
const invoice = invoices[i];
|
||||
// Нормализуем ID инвойса из API
|
||||
let invoiceId = String(invoice.id).trim();
|
||||
|
||||
// Проверяем, есть ли запись с таким ID в E-Taxes Purchase
|
||||
if (!importedInvoices.hasOwnProperty(invoiceId)) {
|
||||
filteredInvoices.push(invoice);
|
||||
} else {
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Скрываем диалог загрузки
|
||||
hide_loading_dialog();
|
||||
|
||||
if (filteredInvoices.length > 0) {
|
||||
// Если есть не импортированные инвойсы, показываем их для выбора
|
||||
show_invoice_selection_dialog(frm, filteredInvoices, main_token, warehouse);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Информация'),
|
||||
indicator: 'blue',
|
||||
message: __('За указанный период не найдено новых счетов-фактур (все ' +
|
||||
invoices.length + ' уже импортированы)')
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error processing purchase data:", e);
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: __('Произошла ошибка при обработке данных: ') + e.message
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(err) {
|
||||
console.error("Error fetching purchases:", err);
|
||||
hide_loading_dialog();
|
||||
|
||||
// В случае ошибки показываем все инвойсы без фильтрации
|
||||
show_invoice_selection_dialog(frm, invoices, main_token, warehouse);
|
||||
|
||||
frappe.show_alert({
|
||||
message: __('Не удалось выполнить проверку дубликатов. Показаны все инвойсы.'),
|
||||
indicator: 'orange'
|
||||
}, 5);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Скрываем диалог загрузки
|
||||
hide_loading_dialog();
|
||||
|
||||
frappe.msgprint({
|
||||
title: __('Информация'),
|
||||
indicator: 'blue',
|
||||
message: __('За указанный период не найдено счетов-фактур')
|
||||
});
|
||||
}
|
||||
} else if (r.message && r.message.error === 'unauthorized') {
|
||||
// Если ошибка авторизации, предлагаем авторизоваться
|
||||
hide_loading_dialog();
|
||||
|
||||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
// Запускаем процесс аутентификации и после успеха повторяем загрузку
|
||||
start_authentication_process(function() {
|
||||
// После успешной аутентификации повторяем загрузку
|
||||
load_etaxes_invoices(frm, fromDate, toDate, warehouse);
|
||||
});
|
||||
},
|
||||
function() {
|
||||
// Если пользователь отказался, показываем сообщение
|
||||
frappe.show_alert({
|
||||
message: __('Authentication canceled. Operation cannot be completed.'),
|
||||
indicator: 'red'
|
||||
}, 5);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Скрываем диалог загрузки
|
||||
hide_loading_dialog();
|
||||
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: r.message ? r.message.message : __('Ошибка загрузки счетов-фактур')
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error fetching invoices:", error);
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка сети'),
|
||||
indicator: 'red',
|
||||
message: __('Ошибка при получении данных с сервера: ') + (error || 'Unknown error')
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: __('Не найдены настройки авторизации E-Taxes')
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error fetching Asan login settings:", error);
|
||||
hide_loading_dialog();
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка сети'),
|
||||
indicator: 'red',
|
||||
message: __('Ошибка при получении настроек авторизации: ') + (error || 'Unknown error')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для отображения диалога с выбором счета-фактуры
|
||||
function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
||||
// Создаем таблицу с инвойсами
|
||||
|
|
@ -830,7 +1038,15 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
|||
|
||||
invoices.forEach(function(invoice) {
|
||||
let serialNumber = invoice.serialNumber || '';
|
||||
let creationDate = invoice.creationDate ? moment(invoice.creationDate).format('DD.MM.YYYY') : '';
|
||||
|
||||
// Используем createdAt для отображения даты, с запасным вариантом creationDate
|
||||
let creationDate = '';
|
||||
if (invoice.createdAt) {
|
||||
creationDate = moment(invoice.createdAt).format('DD.MM.YYYY');
|
||||
} else if (invoice.creationDate) {
|
||||
creationDate = moment(invoice.creationDate).format('DD.MM.YYYY');
|
||||
}
|
||||
|
||||
let senderName = invoice.sender ? invoice.sender.name : (invoice.senderName || '');
|
||||
let amount = invoice.totalAmount || invoice.amount || 0;
|
||||
|
||||
|
|
@ -845,10 +1061,20 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
|||
|
||||
invoice_table += '</tbody></table></div>';
|
||||
|
||||
// Информация о количестве найденных инвойсов
|
||||
let info_message = '<div class="alert alert-info">' +
|
||||
__('Найдено новых счетов-фактур: ') + invoices.length +
|
||||
'</div>';
|
||||
|
||||
// Создаем диалог
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __('Выбор счета-фактуры'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'info_html',
|
||||
fieldtype: 'HTML',
|
||||
options: info_message
|
||||
},
|
||||
{
|
||||
fieldname: 'invoices_html',
|
||||
fieldtype: 'HTML',
|
||||
|
|
@ -882,7 +1108,12 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
|||
d.hide();
|
||||
|
||||
// Загружаем выбранные инвойсы последовательно
|
||||
load_selected_invoices(frm, selected_invoice_ids, token, warehouse);
|
||||
// Показываем диалог с прогрессом
|
||||
show_loading_dialog(__('Загрузка инвойсов...'), true);
|
||||
update_progress(0, selected_invoice_ids.length);
|
||||
|
||||
// Загружаем выбранные инвойсы последовательно
|
||||
load_selected_invoices(frm, selected_invoice_ids, token, warehouse, 0);
|
||||
},
|
||||
secondary_action_label: __('Отмена'),
|
||||
secondary_action: function() {
|
||||
|
|
@ -899,14 +1130,36 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
|||
});
|
||||
}
|
||||
|
||||
// Функция для загрузки выбранных инвойсов
|
||||
function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||
if (invoice_ids.length === 0) {
|
||||
// Если все инвойсы обработаны, обновляем документ
|
||||
if (frm) {
|
||||
frm.reload_doc();
|
||||
// Функция для загрузки выбранных инвойсов с прогресс-баром и механизмом отмены
|
||||
function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count = 0) {
|
||||
// Если все инвойсы обработаны или был запрос на отмену
|
||||
if (invoice_ids.length === 0 || cancel_loading) {
|
||||
// Если был запрос на отмену, показываем соответствующее сообщение
|
||||
if (cancel_loading) {
|
||||
set_loading_success(
|
||||
__('Загрузка отменена'),
|
||||
__('Загружено ' + processed_count + ' инвойсов из ' + (processed_count + invoice_ids.length)),
|
||||
function() {
|
||||
if (frm) {
|
||||
frm.reload_doc();
|
||||
} else {
|
||||
cur_list.refresh();
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
cur_list.refresh();
|
||||
// Все успешно загружено
|
||||
set_loading_success(
|
||||
__('Загрузка завершена'),
|
||||
__('Загружено ' + processed_count + ' инвойсов'),
|
||||
function() {
|
||||
if (frm) {
|
||||
frm.reload_doc();
|
||||
} else {
|
||||
cur_list.refresh();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -914,8 +1167,14 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
// Берем первый ID из списка
|
||||
var invoice_id = invoice_ids.shift();
|
||||
|
||||
// Показываем диалог загрузки
|
||||
show_loading_dialog(__('Загрузка данных счета-фактуры (' + (invoice_ids.length + 1) + ' осталось)...'));
|
||||
// Обновляем прогресс-бар
|
||||
update_progress(processed_count + 1, processed_count + invoice_ids.length + 1);
|
||||
|
||||
// Обновляем текст загрузки
|
||||
update_loading_message(
|
||||
__('Загрузка инвойса (' + (processed_count + 1) + ' из ' + (processed_count + invoice_ids.length + 1) + ')'),
|
||||
__('Получение данных счета-фактуры...')
|
||||
);
|
||||
|
||||
// Получаем детали счета-фактуры
|
||||
frappe.call({
|
||||
|
|
@ -925,13 +1184,24 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
'invoice_id': invoice_id
|
||||
},
|
||||
callback: function(r) {
|
||||
// Закрываем диалог загрузки
|
||||
hide_loading_dialog();
|
||||
if (cancel_loading) {
|
||||
// Если был запрос на отмену во время запроса, прекращаем загрузку
|
||||
load_selected_invoices(frm, [], token, warehouse, processed_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.message && !r.message.error) {
|
||||
// Получаем дату создания инвойса
|
||||
let creationDate = r.message.creationDate || r.message.date || frappe.datetime.nowdate();
|
||||
let scheduleDate = moment(creationDate).format('YYYY-MM-DD');
|
||||
let serialNumber = r.message.serialNumber || '';
|
||||
let senderName = r.message.sender ? r.message.sender.name : '';
|
||||
let total = r.message.totalAmount || r.message.amount || 0;
|
||||
|
||||
update_loading_message(
|
||||
__('Загрузка инвойса (' + (processed_count + 1) + ' из ' + (processed_count + invoice_ids.length + 1) + ')'),
|
||||
__('Импорт данных инвойса: ') + serialNumber
|
||||
);
|
||||
|
||||
// Импортируем данные инвойса с указанием даты и склада
|
||||
frappe.call({
|
||||
|
|
@ -943,23 +1213,80 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
'warehouse': warehouse // Передаем склад в API
|
||||
},
|
||||
callback: function(import_r) {
|
||||
if (cancel_loading) {
|
||||
// Если был запрос на отмену во время запроса, прекращаем загрузку
|
||||
load_selected_invoices(frm, [], token, warehouse, processed_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (import_r.message && import_r.message.success) {
|
||||
// Показываем уведомление об успешном импорте
|
||||
frappe.show_alert({
|
||||
message: __('Инвойс успешно импортирован: ' + r.message.serialNumber),
|
||||
message: __('Инвойс успешно импортирован: ') + serialNumber,
|
||||
indicator: 'green'
|
||||
}, 3);
|
||||
|
||||
// Продолжаем с обработкой следующего инвойса
|
||||
setTimeout(function() {
|
||||
// Если создан новый заказ, обновляем ссылку на документ
|
||||
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
||||
frm = null;
|
||||
// Получаем имя созданного Purchase Order
|
||||
let purchase_order_name = import_r.message.purchase_order;
|
||||
|
||||
// Сохраняем информацию в E-Taxes Purchase
|
||||
|
||||
frappe.call({
|
||||
method: 'invoice_az.api.create_etaxes_purchase',
|
||||
args: {
|
||||
'etaxes_id': invoice_id,
|
||||
'date': scheduleDate,
|
||||
'party': senderName,
|
||||
'total': total
|
||||
},
|
||||
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: {
|
||||
'purchase_order': purchase_order_name,
|
||||
'etaxes_purchase': etaxes_r.message.name
|
||||
},
|
||||
callback: function(link_r) {
|
||||
|
||||
// Увеличиваем счетчик обработанных инвойсов
|
||||
processed_count++;
|
||||
|
||||
// Продолжаем с обработкой следующего инвойса
|
||||
setTimeout(function() {
|
||||
// Если создан новый заказ, обновляем ссылку на документ
|
||||
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
||||
frm = null;
|
||||
}
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Обрабатываем ошибку создания E-Taxes Purchase
|
||||
frappe.show_alert({
|
||||
message: __('Ошибка при создании E-Taxes Purchase'),
|
||||
indicator: 'red'
|
||||
}, 3);
|
||||
|
||||
// Всё равно увеличиваем счетчик и переходим к следующему
|
||||
processed_count++;
|
||||
setTimeout(function() {
|
||||
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
||||
frm = null;
|
||||
}
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||||
}, 1000);
|
||||
});
|
||||
} else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties)) {
|
||||
// Если есть несопоставленные элементы или контрагенты
|
||||
hide_loading_dialog();
|
||||
|
||||
if (import_r.message.unmatched_items) {
|
||||
show_unmatched_items_dialog(r.message, import_r.message.unmatched_items);
|
||||
} else if (import_r.message.unmatched_parties) {
|
||||
|
|
@ -973,21 +1300,22 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
message: __('Для продолжения необходимо создать недостающие сопоставления')
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: import_r.message ? import_r.message.message : __('Ошибка импорта данных счета-фактуры')
|
||||
});
|
||||
frappe.show_alert({
|
||||
message: __('Ошибка импорта: ') + serialNumber,
|
||||
indicator: 'red'
|
||||
}, 3);
|
||||
|
||||
// Переходим к следующему инвойсу
|
||||
// Переходим к следующему инвойсу, не увеличивая счетчик успешно обработанных
|
||||
setTimeout(function() {
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||||
}, 1000);
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (r.message && r.message.error === 'unauthorized') {
|
||||
// Если ошибка авторизации, предлагаем авторизоваться заново
|
||||
hide_loading_dialog();
|
||||
|
||||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
|
|
@ -998,8 +1326,15 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
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) {
|
||||
// Показываем диалог с прогрессом снова
|
||||
show_loading_dialog(__('Возобновление загрузки инвойсов...'), true);
|
||||
update_progress(processed_count, processed_count + invoice_ids.length + 1);
|
||||
|
||||
// Возвращаем текущий инвойс в начало списка
|
||||
invoice_ids.unshift(invoice_id);
|
||||
|
||||
// Используем новый токен для загрузки
|
||||
load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse);
|
||||
load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse, processed_count);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
|
|
@ -1020,16 +1355,15 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
|||
}
|
||||
);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Ошибка'),
|
||||
indicator: 'red',
|
||||
message: r.message ? r.message.message : __('Ошибка загрузки данных счета-фактуры')
|
||||
});
|
||||
frappe.show_alert({
|
||||
message: __('Ошибка загрузки данных инвойса'),
|
||||
indicator: 'red'
|
||||
}, 3);
|
||||
|
||||
// Переходим к следующему инвойсу
|
||||
setTimeout(function() {
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||||
}, 1000);
|
||||
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,23 +6,23 @@ app_email = "info@jeyerp.az"
|
|||
app_license = "unlicense"
|
||||
|
||||
doctype_js = {
|
||||
"Purchase Order": "public/js/purchase_order.js"
|
||||
"Purchase Order": "client/purchase_order.js"
|
||||
}
|
||||
|
||||
page_js = {
|
||||
"purchase-order": "public/js/purchase_order.js"
|
||||
}
|
||||
|
||||
app_include_js = [
|
||||
"/assets/invoice_az/js/purchase_order.js"
|
||||
]
|
||||
|
||||
doctype_list_js = {
|
||||
"E-Taxes Item": "client/e_taxes_items_list.js",
|
||||
"E-Taxes Parties": "client/e_taxes_parties_list.js",
|
||||
"Purchase Order": "client/purchase_order.js"
|
||||
}
|
||||
|
||||
# Хуки для добавления обработчиков событий Purchase Order
|
||||
doc_events = {
|
||||
"Purchase Order": {
|
||||
"on_trash": "invoice_az.api.on_delete_purchase_order",
|
||||
"on_cancel": "invoice_az.api.on_delete_purchase_order"
|
||||
}
|
||||
}
|
||||
|
||||
# Apps
|
||||
# ------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Item Name",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
|
|
@ -31,37 +32,43 @@
|
|||
"fieldname": "etaxes_item_code",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Code",
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "etaxes_unit",
|
||||
"fieldtype": "Data",
|
||||
"label": "Unit of Measure"
|
||||
"label": "Unit of Measure",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "etaxes_price",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Price",
|
||||
"options": "AZN"
|
||||
"options": "AZN",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Source invoice number",
|
||||
"fieldname": "source_invoice",
|
||||
"fieldtype": "Data",
|
||||
"label": "Source"
|
||||
"label": "Source",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "New",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "New\nMapped\nProcessing"
|
||||
"options": "New\nMapped\nProcessing",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "mapped_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mapped Item",
|
||||
"options": "Item"
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_creation_section",
|
||||
|
|
@ -72,24 +79,27 @@
|
|||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Group",
|
||||
"options": "Item Group"
|
||||
"options": "Item Group",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Unit of Measure",
|
||||
"options": "UOM"
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_tax_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Template",
|
||||
"options": "Item Tax Template"
|
||||
"options": "Item Tax Template",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-06 19:24:47.211911",
|
||||
"modified": "2025-05-15 14:25:13.578790",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Invoice Az",
|
||||
"name": "E-Taxes Item",
|
||||
|
|
|
|||
|
|
@ -24,51 +24,59 @@
|
|||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Party Name",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "etaxes_tax_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tax ID"
|
||||
"label": "Tax ID",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Sender",
|
||||
"fieldname": "etaxes_party_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "E-Taxes Type",
|
||||
"options": "Sender\nReceiver"
|
||||
"options": "Sender\nReceiver",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "etaxes_address",
|
||||
"fieldtype": "Data",
|
||||
"label": "Address"
|
||||
"label": "Address",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Source invoice number",
|
||||
"fieldname": "source_invoice",
|
||||
"fieldtype": "Data",
|
||||
"label": "Source"
|
||||
"label": "Source",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "New",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "New\nMapped\nProcessing"
|
||||
"options": "New\nMapped\nProcessing",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Supplier",
|
||||
"fieldname": "erp_party_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "System Party Type",
|
||||
"options": "Supplier\nCustomer"
|
||||
"options": "Supplier\nCustomer",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "mapped_party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Mapped Party",
|
||||
"options": "erp_party_type"
|
||||
"options": "erp_party_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party_creation_section",
|
||||
|
|
@ -79,7 +87,8 @@
|
|||
"fieldname": "party_group",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Party Group",
|
||||
"options": "party_group_type"
|
||||
"options": "party_group_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Supplier Group",
|
||||
|
|
@ -87,18 +96,20 @@
|
|||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Party Group Type",
|
||||
"options": "Supplier Group\nCustomer Group"
|
||||
"options": "Supplier Group\nCustomer Group",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_terms",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Terms",
|
||||
"options": "Payment Terms Template"
|
||||
"options": "Payment Terms Template",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-09 15:46:11.179417",
|
||||
"modified": "2025-05-15 14:25:54.517365",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Invoice Az",
|
||||
"name": "E-Taxes Parties",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2025, Jey ERP and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("E-Taxes Purchase", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-05-14 16:07:06.007893",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"date",
|
||||
"total",
|
||||
"party",
|
||||
"etaxes_id"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Data",
|
||||
"label": "date"
|
||||
},
|
||||
{
|
||||
"fieldname": "total",
|
||||
"fieldtype": "Data",
|
||||
"label": "total"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Data",
|
||||
"label": "party"
|
||||
},
|
||||
{
|
||||
"fieldname": "etaxes_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "etaxes_id"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-14 16:14:55.664290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Invoice Az",
|
||||
"name": "E-Taxes Purchase",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2025, Jey ERP and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ETaxesPurchase(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2025, Jey ERP and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record depdendencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class UnitTestETaxesPurchase(UnitTestCase):
|
||||
"""
|
||||
Unit tests for ETaxesPurchase.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IntegrationTestETaxesPurchase(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for ETaxesPurchase.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -154,8 +154,18 @@ frappe.ui.form.on('E-Taxes Settings', {
|
|||
// Добавляем несопоставленные товары в таблицу
|
||||
r.message.items.forEach(function(item) {
|
||||
let row = frm.add_child('item_mappings');
|
||||
// ИСПРАВЛЕНИЕ: Используем ID документа (name) вместо отображаемого имени
|
||||
|
||||
// Используем ID документа (name) вместо отображаемого имени
|
||||
row.etaxes_item_name = item.name; // Должен быть ID документа
|
||||
|
||||
// Добавляем все значения по умолчанию
|
||||
if (item.item_group) row.item_group = item.item_group;
|
||||
if (item.uom) row.uom = item.uom;
|
||||
if (item.is_stock_item !== undefined) row.is_stock_item = item.is_stock_item;
|
||||
if (item.is_purchase_item !== undefined) row.is_purchase_item = item.is_purchase_item;
|
||||
if (item.item_tax_template) row.item_tax_template = item.item_tax_template;
|
||||
|
||||
// Тип сопоставления
|
||||
row.mapping_type = 'Manual';
|
||||
});
|
||||
frm.refresh_field('item_mappings');
|
||||
|
|
@ -329,9 +339,29 @@ frappe.ui.form.on('E-Taxes Settings', {
|
|||
// Добавляем несопоставленных контрагентов в таблицу
|
||||
r.message.parties.forEach(function(party) {
|
||||
let row = frm.add_child('party_mappings');
|
||||
|
||||
// Основные поля
|
||||
row.etaxes_party_name = party.etaxes_party_name;
|
||||
row.etaxes_tax_id = party.etaxes_tax_id;
|
||||
row.party_type = party.etaxes_party_type === 'Sender' ? 'Supplier' : 'Customer';
|
||||
|
||||
// Тип контрагента
|
||||
row.party_type = party.erp_party_type ||
|
||||
(party.etaxes_party_type === 'Sender' ? 'Supplier' : 'Customer');
|
||||
|
||||
// Настройки по умолчанию в зависимости от типа
|
||||
if (party.supplier_group && row.party_type === 'Supplier')
|
||||
row.supplier_group = party.supplier_group;
|
||||
|
||||
if (party.customer_group && row.party_type === 'Customer')
|
||||
row.customer_group = party.customer_group;
|
||||
|
||||
if (party.territory && row.party_type === 'Customer')
|
||||
row.territory = party.territory;
|
||||
|
||||
if (party.payment_terms)
|
||||
row.payment_terms = party.payment_terms;
|
||||
|
||||
// Тип сопоставления
|
||||
row.mapping_type = 'Manual';
|
||||
});
|
||||
frm.refresh_field('party_mappings');
|
||||
|
|
|
|||
Loading…
Reference in New Issue