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):
|
def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
"""Loading parties from invoices for a period"""
|
"""Loading parties from invoices for a period"""
|
||||||
try:
|
try:
|
||||||
frappe.log_error(f"=== НАЧАЛО ЗАГРУЗКИ КОНТРАГЕНТОВ ===\nПараметры: date_from={date_from}, date_to={date_to}, max_count={max_count}", "Party Load Start")
|
|
||||||
|
|
||||||
# Счетчики для отслеживания
|
# Счетчики для отслеживания
|
||||||
created_count = 0
|
created_count = 0
|
||||||
skipped_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()
|
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'):
|
if not asan_login_settings.get('found'):
|
||||||
frappe.log_error("Asan Login settings not found!", "Party Load Error")
|
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'No Asan Login settings found'
|
'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"]
|
"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))
|
response = get_invoices(asan_login_settings['main_token'], json.dumps(filters))
|
||||||
|
|
||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
frappe.log_error(f"API вернул ошибку: {response['error']}", "API Error")
|
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': response['error']
|
'message': response['error']
|
||||||
|
|
@ -1108,10 +1102,8 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
|
|
||||||
# Extract data from response
|
# Extract data from response
|
||||||
invoices_data = response.get('data', []) or response.get('invoices', [])
|
invoices_data = response.get('data', []) or response.get('invoices', [])
|
||||||
frappe.log_error(f"Получено инвойсов: {len(invoices_data)}", "Invoices Count")
|
|
||||||
|
|
||||||
if len(invoices_data) == 0:
|
if len(invoices_data) == 0:
|
||||||
frappe.log_error(f"API вернул пустой список инвойсов! Полный ответ API: {response}", "Empty Invoices List")
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'created_count': 0,
|
'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'
|
'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 = {}
|
unique_parties = {}
|
||||||
|
|
||||||
|
|
@ -1133,21 +1121,18 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
for invoice in invoices_data:
|
for invoice in invoices_data:
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
invoice_id = invoice.get('id', 'Нет ID')
|
invoice_id = invoice.get('id', 'Нет ID')
|
||||||
frappe.log_error(f"Обработка инвойса #{processed_count}, ID: {invoice_id}", f"Invoice {invoice_id}")
|
|
||||||
|
|
||||||
serial_number = invoice.get('serialNumber', '')
|
serial_number = invoice.get('serialNumber', '')
|
||||||
|
|
||||||
# Обработка отправителя (Sender -> Supplier)
|
# Обработка отправителя (Sender -> Supplier)
|
||||||
sender = invoice.get('sender', {})
|
sender = invoice.get('sender', {})
|
||||||
frappe.log_error(f"Поля sender в инвойсе {invoice_id}: {json.dumps(sender)}", f"Sender Data {invoice_id}")
|
|
||||||
|
|
||||||
if sender:
|
if sender:
|
||||||
sender_name = sender.get('name', '')
|
sender_name = sender.get('name', '')
|
||||||
sender_tin = sender.get('tin', '')
|
sender_tin = sender.get('tin', '')
|
||||||
sender_pin = sender.get('pin', '')
|
sender_address = sender.get('address', '')
|
||||||
|
|
||||||
if not sender_name or not sender_tin:
|
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
|
continue
|
||||||
|
|
||||||
# Создаем ключ для проверки уникальности
|
# Создаем ключ для проверки уникальности
|
||||||
|
|
@ -1157,27 +1142,22 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
unique_parties[sender_key] = {
|
unique_parties[sender_key] = {
|
||||||
'etaxes_party_name': sender_name,
|
'etaxes_party_name': sender_name,
|
||||||
'etaxes_tax_id': sender_tin,
|
'etaxes_tax_id': sender_tin,
|
||||||
'etaxes_pin': sender_pin,
|
'etaxes_address': sender_address,
|
||||||
'etaxes_party_type': 'Sender',
|
'etaxes_party_type': 'Sender',
|
||||||
'erp_party_type': 'Supplier',
|
'erp_party_type': 'Supplier',
|
||||||
'source_invoice': serial_number,
|
'source_invoice': serial_number,
|
||||||
'status': 'New'
|
'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 -> Customer)
|
||||||
receiver = invoice.get('receiver', {})
|
receiver = invoice.get('receiver', {})
|
||||||
frappe.log_error(f"Поля receiver в инвойсе {invoice_id}: {json.dumps(receiver)}", f"Receiver Data {invoice_id}")
|
|
||||||
|
|
||||||
if receiver:
|
if receiver:
|
||||||
receiver_name = receiver.get('name', '')
|
receiver_name = receiver.get('name', '')
|
||||||
receiver_tin = receiver.get('tin', '')
|
receiver_tin = receiver.get('tin', '')
|
||||||
receiver_pin = receiver.get('pin', '')
|
receiver_address = receiver.get('address', '')
|
||||||
|
|
||||||
if not receiver_name or not receiver_tin:
|
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
|
continue
|
||||||
|
|
||||||
# Создаем ключ для проверки уникальности
|
# Создаем ключ для проверки уникальности
|
||||||
|
|
@ -1187,27 +1167,12 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
unique_parties[receiver_key] = {
|
unique_parties[receiver_key] = {
|
||||||
'etaxes_party_name': receiver_name,
|
'etaxes_party_name': receiver_name,
|
||||||
'etaxes_tax_id': receiver_tin,
|
'etaxes_tax_id': receiver_tin,
|
||||||
'etaxes_pin': receiver_pin,
|
'etaxes_address': receiver_address,
|
||||||
'etaxes_party_type': 'Receiver',
|
'etaxes_party_type': 'Receiver',
|
||||||
'erp_party_type': 'Customer',
|
'erp_party_type': 'Customer',
|
||||||
'source_invoice': serial_number,
|
'source_invoice': serial_number,
|
||||||
'status': 'New'
|
'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():
|
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})
|
existing = frappe.db.exists('E-Taxes Parties', {'etaxes_party_name': party_name})
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
frappe.log_error(f"Контрагент уже существует: {party_name} ({party_tin})", "Existing Party")
|
|
||||||
skipped_count += 1
|
skipped_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -1228,26 +1192,18 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
'doctype': 'E-Taxes Parties',
|
'doctype': 'E-Taxes Parties',
|
||||||
'etaxes_party_name': party_name,
|
'etaxes_party_name': party_name,
|
||||||
'etaxes_tax_id': party_tin,
|
'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'),
|
'etaxes_party_type': party_data.get('etaxes_party_type', 'Sender'),
|
||||||
'erp_party_type': party_data.get('erp_party_type', 'Supplier'),
|
'erp_party_type': party_data.get('erp_party_type', 'Supplier'),
|
||||||
'source_invoice': party_data.get('source_invoice', ''),
|
'source_invoice': party_data.get('source_invoice', ''),
|
||||||
'status': 'New'
|
'status': 'New'
|
||||||
})
|
})
|
||||||
|
|
||||||
frappe.log_error(f"Создание контрагента с данными: {json.dumps(doc.as_dict())}", f"Create Party {party_tin}")
|
|
||||||
|
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert(ignore_permissions=True)
|
||||||
|
|
||||||
frappe.log_error(f"Контрагент успешно создан: {party_name} ({party_tin})", "Party Created")
|
|
||||||
created_count += 1
|
created_count += 1
|
||||||
except Exception as e:
|
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
|
failed_count += 1
|
||||||
|
|
||||||
frappe.log_error(f"=== ИТОГ ЗАГРУЗКИ КОНТРАГЕНТОВ ===\nСоздано: {created_count}\nПропущено: {skipped_count}\nОшибки: {failed_count}", "Loading Results")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'created_count': created_count,
|
'created_count': created_count,
|
||||||
|
|
@ -1258,15 +1214,12 @@ def load_parties_from_invoices(date_from, date_to, max_count=200):
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
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 {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': str(e)
|
'message': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_settings():
|
def get_default_settings():
|
||||||
"""Получение активных настроек E-Taxes"""
|
"""Получение активных настроек E-Taxes"""
|
||||||
|
|
@ -1376,22 +1329,34 @@ def get_unmapped_items():
|
||||||
# 1. Получаем все товары со статусом 'New'
|
# 1. Получаем все товары со статусом 'New'
|
||||||
etaxes_items = frappe.get_all('E-Taxes Item',
|
etaxes_items = frappe.get_all('E-Taxes Item',
|
||||||
filters={'status': 'New'},
|
filters={'status': 'New'},
|
||||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
|
fields=['name', 'etaxes_item_name', 'etaxes_item_code', 'etaxes_unit', 'etaxes_price'])
|
||||||
|
|
||||||
# 2. Проверяем, нет ли их уже в настройках
|
# 2. Проверяем, нет ли их уже в настройках
|
||||||
existing_mappings = {}
|
existing_mappings = {}
|
||||||
for mapping in settings.item_mappings:
|
for mapping in settings.item_mappings:
|
||||||
existing_mappings[mapping.etaxes_item_name] = True
|
existing_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||||
|
|
||||||
# 3. Добавляем только те, которых нет в настройках
|
# 3. Добавляем только те, которых нет в настройках
|
||||||
for item in etaxes_items:
|
for item in etaxes_items:
|
||||||
# В E-Taxes Item name равен etaxes_item_code из-за настройки autoname
|
|
||||||
if item.name not in existing_mappings:
|
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_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 {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|
@ -1407,9 +1372,6 @@ def get_unmapped_items():
|
||||||
def get_unmapped_parties():
|
def get_unmapped_parties():
|
||||||
"""Получение несопоставленных контрагентов из E-Taxes Parties"""
|
"""Получение несопоставленных контрагентов из E-Taxes Parties"""
|
||||||
try:
|
try:
|
||||||
# Получаем всех контрагентов из E-Taxes Parties без сопоставления
|
|
||||||
unmapped_parties = []
|
|
||||||
|
|
||||||
# Получаем активные настройки
|
# Получаем активные настройки
|
||||||
settings = get_active_settings()
|
settings = get_active_settings()
|
||||||
if not settings:
|
if not settings:
|
||||||
|
|
@ -1418,6 +1380,9 @@ def get_unmapped_parties():
|
||||||
'message': 'Не найдены активные настройки E-Taxes'
|
'message': 'Не найдены активные настройки E-Taxes'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Получаем всех контрагентов из E-Taxes Parties без сопоставления
|
||||||
|
unmapped_parties = []
|
||||||
|
|
||||||
# Получаем список существующих сопоставлений
|
# Получаем список существующих сопоставлений
|
||||||
existing_mappings = {}
|
existing_mappings = {}
|
||||||
for mapping in settings.party_mappings:
|
for mapping in settings.party_mappings:
|
||||||
|
|
@ -1426,18 +1391,42 @@ def get_unmapped_parties():
|
||||||
# Получаем несопоставленные контрагенты из E-Taxes Parties
|
# Получаем несопоставленные контрагенты из E-Taxes Parties
|
||||||
etaxes_parties = frappe.get_all('E-Taxes Parties',
|
etaxes_parties = frappe.get_all('E-Taxes Parties',
|
||||||
filters={'status': 'New'},
|
filters={'status': 'New'},
|
||||||
fields=['etaxes_party_name', 'etaxes_tax_id',
|
fields=['name', 'etaxes_party_name', 'etaxes_tax_id',
|
||||||
'etaxes_party_type', 'erp_party_type'])
|
'etaxes_party_type', 'erp_party_type', 'etaxes_address', 'source_invoice'])
|
||||||
|
|
||||||
# Фильтруем только те, которых нет в маппингах
|
# Фильтруем только те, которых нет в маппингах
|
||||||
for party in etaxes_parties:
|
for party in etaxes_parties:
|
||||||
if party.etaxes_party_name not in existing_mappings:
|
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_party_name': party.etaxes_party_name,
|
||||||
'etaxes_tax_id': party.etaxes_tax_id,
|
'etaxes_tax_id': party.etaxes_tax_id,
|
||||||
'etaxes_party_type': party.etaxes_party_type,
|
'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 {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|
@ -1449,7 +1438,6 @@ def get_unmapped_parties():
|
||||||
'message': str(e)
|
'message': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def match_similar_items():
|
def match_similar_items():
|
||||||
"""Сопоставление товаров по похожим названиям"""
|
"""Сопоставление товаров по похожим названиям"""
|
||||||
|
|
@ -1767,58 +1755,53 @@ def match_similar_parties():
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_unmapped_items(settings_name):
|
def create_unmapped_items(settings_name):
|
||||||
"""Создание товаров для всех несопоставленных элементов из E-Taxes"""
|
"""Создание товаров для несопоставленных элементов из табличной части настроек E-Taxes"""
|
||||||
try:
|
try:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Получаем настройки
|
# Получаем настройки
|
||||||
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
||||||
|
|
||||||
# Получаем несопоставленные товары
|
# Получаем элементы из табличной части, у которых нет сопоставления
|
||||||
etaxes_items = frappe.get_all('E-Taxes Item',
|
unmapped_items = []
|
||||||
filters={'status': 'New'},
|
|
||||||
fields=['name', 'etaxes_item_name', 'etaxes_item_code'])
|
|
||||||
|
|
||||||
# Логируем полученные товары
|
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)
|
||||||
|
|
||||||
# Получаем существующие маппинги
|
if len(unmapped_items) == 0:
|
||||||
existing_mappings = {}
|
return {
|
||||||
mapping_settings = {} # Для хранения индивидуальных настроек
|
"success": True,
|
||||||
|
"created_count": 0,
|
||||||
|
"error_count": 0,
|
||||||
|
"message": "Нет несопоставленных товаров в табличной части для создания"
|
||||||
|
}
|
||||||
|
|
||||||
|
created_count = 0
|
||||||
|
error_count = 0
|
||||||
|
|
||||||
|
# Собираем индивидуальные настройки для каждого элемента
|
||||||
|
mapping_settings = {}
|
||||||
for mapping in settings_doc.item_mappings:
|
for mapping in settings_doc.item_mappings:
|
||||||
# Используем name (id) документа E-Taxes Item как ключ
|
|
||||||
existing_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
|
||||||
|
|
||||||
# Сохраняем индивидуальные настройки для этого элемента, если они есть
|
|
||||||
item_settings = {}
|
item_settings = {}
|
||||||
if mapping.item_group:
|
if mapping.item_group:
|
||||||
item_settings['item_group'] = mapping.item_group
|
item_settings['item_group'] = mapping.item_group
|
||||||
if mapping.uom:
|
if mapping.uom:
|
||||||
item_settings['uom'] = 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
|
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
|
item_settings['is_purchase_item'] = mapping.is_purchase_item
|
||||||
if mapping.item_tax_template:
|
if mapping.item_tax_template:
|
||||||
item_settings['item_tax_template'] = 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
|
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:
|
for etaxes_item in unmapped_items:
|
||||||
try:
|
try:
|
||||||
# Проверяем, существует ли уже товар с таким именем
|
# Проверяем, существует ли уже товар с таким именем
|
||||||
|
|
@ -1836,7 +1819,9 @@ def create_unmapped_items(settings_name):
|
||||||
if not item_code or len(item_code) < 3:
|
if not item_code or len(item_code) < 3:
|
||||||
item_code = "ITEM-" + frappe.generate_hash(length=8)
|
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_code = item_code
|
||||||
item_doc.item_name = etaxes_item.etaxes_item_name
|
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, {})
|
individual_settings = mapping_settings.get(etaxes_item.name, {})
|
||||||
|
|
||||||
# Применяем настройки с учетом индивидуальных приоритетов
|
# Применяем настройки с учетом индивидуальных приоритетов
|
||||||
item_doc.item_group = individual_settings.get('item_group', settings_doc.default_item_group)
|
item_group = individual_settings.get('item_group', settings_doc.default_item_group)
|
||||||
item_doc.stock_uom = individual_settings.get('uom', settings_doc.default_uom)
|
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
|
# Для булевых полей проверяем, что они не None
|
||||||
if 'is_stock_item' in individual_settings:
|
if 'is_stock_item' in individual_settings:
|
||||||
|
|
@ -1860,39 +1856,32 @@ def create_unmapped_items(settings_name):
|
||||||
else:
|
else:
|
||||||
item_doc.is_purchase_item = getattr(settings_doc, 'is_purchase_item', 1)
|
item_doc.is_purchase_item = getattr(settings_doc, 'is_purchase_item', 1)
|
||||||
|
|
||||||
# Если есть шаблон налогов, применяем его
|
# Правильно добавляем налоговый шаблон
|
||||||
if 'item_tax_template' in individual_settings:
|
if 'item_tax_template' in individual_settings:
|
||||||
item_doc.taxes = [{
|
tax_template = individual_settings['item_tax_template']
|
||||||
"item_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:
|
elif hasattr(settings_doc, 'default_item_tax_template') and settings_doc.default_item_tax_template:
|
||||||
item_doc.taxes = [{
|
tax_template = settings_doc.default_item_tax_template
|
||||||
"item_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()
|
item_doc.insert()
|
||||||
|
|
||||||
# Логируем созданный товар
|
# Обновляем маппинг в настройках
|
||||||
|
for mapping in settings_doc.item_mappings:
|
||||||
# Проверяем, существует ли уже маппинг для этого товара
|
|
||||||
existing_row = None
|
|
||||||
for idx, mapping in enumerate(settings_doc.item_mappings):
|
|
||||||
if mapping.etaxes_item_name == etaxes_item.name:
|
if mapping.etaxes_item_name == etaxes_item.name:
|
||||||
existing_row = idx
|
mapping.erp_item = item_doc.name
|
||||||
|
mapping.mapping_type = 'Automatic'
|
||||||
break
|
break
|
||||||
|
|
||||||
if existing_row is not None:
|
# Обновляем статус E-Taxes Item
|
||||||
# Если строка уже существует, обновляем её значение erp_item
|
etaxes_item.status = 'Mapped'
|
||||||
settings_doc.item_mappings[existing_row].erp_item = item_doc.name
|
etaxes_item.mapped_item = item_doc.name
|
||||||
settings_doc.item_mappings[existing_row].mapping_type = 'Automatic'
|
etaxes_item.save()
|
||||||
else:
|
|
||||||
# Если строки нет, добавляем новую
|
|
||||||
settings_doc.append('item_mappings', {
|
|
||||||
'etaxes_item_name': etaxes_item.name, # Используем ID документа
|
|
||||||
'erp_item': item_doc.name,
|
|
||||||
'mapping_type': 'Automatic'
|
|
||||||
})
|
|
||||||
|
|
||||||
created_count += 1
|
created_count += 1
|
||||||
|
|
||||||
|
|
@ -1903,18 +1892,6 @@ def create_unmapped_items(settings_name):
|
||||||
if created_count > 0:
|
if created_count > 0:
|
||||||
settings_doc.save()
|
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 {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"created_count": created_count,
|
"created_count": created_count,
|
||||||
|
|
@ -1928,51 +1905,6 @@ def create_unmapped_items(settings_name):
|
||||||
"message": str(e)
|
"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()
|
@frappe.whitelist()
|
||||||
def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule_date=None, warehouse=None):
|
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()
|
@frappe.whitelist()
|
||||||
def create_unmapped_parties(settings_name):
|
def create_unmapped_parties(settings_name):
|
||||||
"""Создание контрагентов для всех несопоставленных элементов из E-Taxes Parties"""
|
"""Создание контрагентов для несопоставленных элементов из табличной части настроек E-Taxes"""
|
||||||
try:
|
try:
|
||||||
import re
|
|
||||||
|
|
||||||
# Получаем настройки
|
# Получаем настройки
|
||||||
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
settings_doc = frappe.get_doc("E-Taxes Settings", settings_name)
|
||||||
|
|
||||||
# Получаем несопоставленные контрагенты
|
# Получаем элементы из табличной части, у которых нет сопоставления
|
||||||
etaxes_parties = frappe.get_all('E-Taxes Parties',
|
unmapped_parties = []
|
||||||
filters={'status': 'New'},
|
|
||||||
fields=['name', 'etaxes_party_name', 'etaxes_tax_id',
|
|
||||||
'etaxes_party_type', 'erp_party_type'])
|
|
||||||
|
|
||||||
# Логируем полученные контрагенты
|
for mapping_party in settings_doc.party_mappings:
|
||||||
frappe.log_error(f"E-Taxes parties with status New: {etaxes_parties}", "Create Parties Debug")
|
# Проверяем, что у контрагента нет сопоставления или оно пустое
|
||||||
|
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)
|
||||||
|
|
||||||
# Получаем существующие маппинги
|
if len(unmapped_parties) == 0:
|
||||||
existing_mappings = {}
|
return {
|
||||||
mapping_settings = {} # Для хранения индивидуальных настроек
|
"success": True,
|
||||||
|
"created_count": 0,
|
||||||
|
"error_count": 0,
|
||||||
|
"message": "Нет несопоставленных контрагентов в табличной части для создания"
|
||||||
|
}
|
||||||
|
|
||||||
|
created_count = 0
|
||||||
|
error_count = 0
|
||||||
|
|
||||||
|
# Собираем индивидуальные настройки для каждого элемента
|
||||||
|
mapping_settings = {}
|
||||||
for mapping in settings_doc.party_mappings:
|
for mapping in settings_doc.party_mappings:
|
||||||
# Используем name (id) документа E-Taxes Parties как ключ
|
|
||||||
existing_mappings[mapping.etaxes_party_name] = mapping.erp_party
|
|
||||||
|
|
||||||
# Сохраняем индивидуальные настройки для этого элемента, если они есть
|
|
||||||
party_settings = {}
|
party_settings = {}
|
||||||
if mapping.supplier_group:
|
if mapping.supplier_group:
|
||||||
party_settings['supplier_group'] = mapping.supplier_group
|
party_settings['supplier_group'] = mapping.supplier_group
|
||||||
|
|
@ -2395,54 +2340,39 @@ def create_unmapped_parties(settings_name):
|
||||||
if mapping.payment_terms:
|
if mapping.payment_terms:
|
||||||
party_settings['payment_terms'] = mapping.payment_terms
|
party_settings['payment_terms'] = mapping.payment_terms
|
||||||
|
|
||||||
if party_settings: # Если есть индивидуальные настройки
|
if party_settings:
|
||||||
mapping_settings[mapping.etaxes_party_name] = party_settings
|
key = f"{mapping.etaxes_party_name}|{mapping.etaxes_tax_id}"
|
||||||
|
mapping_settings[key] = 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
|
|
||||||
|
|
||||||
for etaxes_party in unmapped_parties:
|
for etaxes_party in unmapped_parties:
|
||||||
try:
|
try:
|
||||||
# Определяем тип контрагента (Customer/Supplier) на основе erp_party_type
|
# Определяем тип контрагента (Customer/Supplier)
|
||||||
party_type = etaxes_party.erp_party_type
|
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_field = 'supplier_name' if party_type == 'Supplier' else 'customer_name'
|
||||||
party_exists = frappe.db.exists(party_type, {party_field: etaxes_party.etaxes_party_name})
|
party_exists = frappe.db.exists(party_type, {party_field: etaxes_party.etaxes_party_name})
|
||||||
|
|
||||||
if party_exists:
|
if party_exists:
|
||||||
frappe.log_error(f"{party_type} already exists: {etaxes_party.etaxes_party_name}", "Create Parties Debug")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Логируем создаваемого контрагента
|
# Создаем нового контрагента
|
||||||
frappe.log_error(f"Creating {party_type}: {etaxes_party.etaxes_party_name}", "Create Parties Debug")
|
|
||||||
|
|
||||||
# Создаем новый контрагент
|
|
||||||
party_doc = frappe.new_doc(party_type)
|
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":
|
if party_type == "Supplier":
|
||||||
party_doc.supplier_name = etaxes_party.etaxes_party_name
|
party_doc.supplier_name = etaxes_party.etaxes_party_name
|
||||||
party_doc.supplier_type = "Company" # Можно настроить при необходимости
|
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, добавляем его
|
# Если указан tax_id, добавляем его
|
||||||
if etaxes_party.etaxes_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_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:
|
territory = individual_settings.get('territory', "All Territories")
|
||||||
party_doc.territory = individual_settings['territory']
|
if not frappe.db.exists('Territory', territory):
|
||||||
else:
|
territory = "All Territories"
|
||||||
# Можно использовать "All Territories" как дефолтное значение
|
party_doc.territory = territory
|
||||||
party_doc.territory = "All Territories"
|
|
||||||
|
|
||||||
# Если указан tax_id, добавляем его
|
# Если указан tax_id, добавляем его
|
||||||
if etaxes_party.etaxes_tax_id:
|
if etaxes_party.etaxes_tax_id:
|
||||||
|
|
@ -2480,49 +2412,28 @@ def create_unmapped_parties(settings_name):
|
||||||
# Сохраняем контрагента
|
# Сохраняем контрагента
|
||||||
party_doc.insert()
|
party_doc.insert()
|
||||||
|
|
||||||
# Логируем созданного контрагента
|
# Обновляем маппинг в настройках
|
||||||
frappe.log_error(f"{party_type} created successfully: {party_doc.name}", "Create Parties Debug")
|
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
|
||||||
existing_row = None
|
mapping.party_type = party_type
|
||||||
for idx, mapping in enumerate(settings_doc.party_mappings):
|
mapping.mapping_type = 'Automatic'
|
||||||
if mapping.etaxes_party_name == etaxes_party.etaxes_party_name:
|
|
||||||
existing_row = idx
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if existing_row is not None:
|
# Обновляем статус E-Taxes Parties
|
||||||
# Если строка уже существует, обновляем её значение erp_party
|
etaxes_party.status = 'Mapped'
|
||||||
settings_doc.party_mappings[existing_row].erp_party = party_doc.name
|
etaxes_party.mapped_party = party_doc.name
|
||||||
settings_doc.party_mappings[existing_row].party_type = party_type
|
etaxes_party.save()
|
||||||
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()
|
|
||||||
|
|
||||||
created_count += 1
|
created_count += 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error creating party {etaxes_party.etaxes_party_name}: {str(e)}", "Create Parties Debug")
|
|
||||||
error_count += 1
|
error_count += 1
|
||||||
|
|
||||||
# Сохраняем настройки после добавления всех сопоставлений
|
# Сохраняем настройки после добавления всех сопоставлений
|
||||||
if created_count > 0:
|
if created_count > 0:
|
||||||
settings_doc.save()
|
settings_doc.save()
|
||||||
|
|
||||||
frappe.log_error(f"Final result: created {created_count} parties, errors: {error_count}", "Create Parties Debug")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"created_count": created_count,
|
"created_count": created_count,
|
||||||
|
|
@ -2531,7 +2442,6 @@ def create_unmapped_parties(settings_name):
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error in create_unmapped_parties: {str(e)}\n{frappe.get_traceback()}", "Create Parties Debug")
|
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": str(e)
|
"message": str(e)
|
||||||
|
|
@ -2585,3 +2495,144 @@ def check_token_validity(asan_login_name=None):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error in check_token_validity: {str(e)}\n{frappe.get_traceback()}", "Token Validity Check")
|
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', {
|
frappe.ui.form.on('Purchase Order', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.docstatus === 0) {
|
// Проверяем, установлена ли галочка is_taxes_doc
|
||||||
frm.add_custom_button(__('Импорт из E-Taxes'), function() {
|
if (frm.doc.is_taxes_doc) {
|
||||||
show_etaxes_import_dialog(frm);
|
// Делаем все поля 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'] = {
|
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) {
|
get_indicator: function(doc) {
|
||||||
|
|
@ -42,8 +51,11 @@ frappe.listview_settings['Purchase Order'] = {
|
||||||
// Глобальная переменная для хранения диалога загрузки
|
// Глобальная переменная для хранения диалога загрузки
|
||||||
var loading_dialog = null;
|
var loading_dialog = null;
|
||||||
|
|
||||||
// Функция для отображения диалога загрузки
|
// Глобальная переменная для отмены процесса загрузки
|
||||||
function show_loading_dialog(message) {
|
var cancel_loading = false;
|
||||||
|
|
||||||
|
// Функция для отображения диалога загрузки с прогресс-баром
|
||||||
|
function show_loading_dialog(message, show_progress = false) {
|
||||||
if (!loading_dialog) {
|
if (!loading_dialog) {
|
||||||
loading_dialog = new frappe.ui.Dialog({
|
loading_dialog = new frappe.ui.Dialog({
|
||||||
title: __('Загрузка'),
|
title: __('Загрузка'),
|
||||||
|
|
@ -53,7 +65,7 @@ function show_loading_dialog(message) {
|
||||||
fieldtype: 'HTML',
|
fieldtype: 'HTML',
|
||||||
options: `
|
options: `
|
||||||
<div class="text-center">
|
<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="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>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -61,6 +73,31 @@ function show_loading_dialog(message) {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
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>
|
</style>
|
||||||
<div class="mt-3" id="status_message">
|
<div class="mt-3" id="status_message">
|
||||||
<h4>${message || __('Пожалуйста, подождите...')}</h4>
|
<h4>${message || __('Пожалуйста, подождите...')}</h4>
|
||||||
|
|
@ -73,29 +110,79 @@ function show_loading_dialog(message) {
|
||||||
</div>
|
</div>
|
||||||
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
primary_action_label: __('Отмена'),
|
primary_action_label: __('Отмена'),
|
||||||
primary_action: function() {
|
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 {
|
} else {
|
||||||
// Обновляем сообщение
|
// Обновляем сообщение
|
||||||
$('#status_message h4').text(message || __('Пожалуйста, подождите...'));
|
$('#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();
|
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() {
|
function hide_loading_dialog() {
|
||||||
if (loading_dialog) {
|
if (loading_dialog) {
|
||||||
|
try {
|
||||||
loading_dialog.hide();
|
loading_dialog.hide();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error hiding loading dialog:", e);
|
||||||
|
}
|
||||||
loading_dialog = null;
|
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) {
|
function set_loading_success(message, submessage, callback, delay = 2000) {
|
||||||
if (loading_dialog) {
|
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;">
|
<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>
|
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,8 +220,9 @@ function set_loading_success(message, submessage, callback, delay = 2000) {
|
||||||
$('#status_message p').text(submessage);
|
$('#status_message p').text(submessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скрываем код верификации
|
// Скрываем код верификации и прогресс
|
||||||
$('#verification_code_container').hide();
|
$('#verification_code_container').hide();
|
||||||
|
$('#progress_container').hide();
|
||||||
|
|
||||||
// Удаляем кнопки
|
// Удаляем кнопки
|
||||||
loading_dialog.set_primary_action(null);
|
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) {
|
function set_loading_error(message, submessage, show_close_button = true) {
|
||||||
if (loading_dialog) {
|
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;">
|
<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>
|
<i class="fa fa-times" style="font-size: 48px; color: #d9534f;"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -169,8 +257,9 @@ function set_loading_error(message, submessage, show_close_button = true) {
|
||||||
$('#status_message p').text(submessage);
|
$('#status_message p').text(submessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скрываем код верификации
|
// Скрываем код верификации и прогресс
|
||||||
$('#verification_code_container').hide();
|
$('#verification_code_container').hide();
|
||||||
|
$('#progress_container').hide();
|
||||||
|
|
||||||
// При необходимости добавляем кнопку закрытия
|
// При необходимости добавляем кнопку закрытия
|
||||||
if (show_close_button) {
|
if (show_close_button) {
|
||||||
|
|
@ -353,7 +442,6 @@ function poll_authentication_status(asan_login_name, bearer_token, callback) {
|
||||||
},
|
},
|
||||||
error: function(err) {
|
error: function(err) {
|
||||||
// В случае ошибки продолжаем опрос
|
// В случае ошибки продолжаем опрос
|
||||||
console.error('Error during status check:', err);
|
|
||||||
setTimeout(pollStatus, 6000);
|
setTimeout(pollStatus, 6000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -440,7 +528,6 @@ function complete_authentication(asan_login_name, callback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} 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);
|
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||||||
}
|
}
|
||||||
|
|
@ -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() {
|
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) {
|
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) {
|
invoices.forEach(function(invoice) {
|
||||||
let serialNumber = invoice.serialNumber || '';
|
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 senderName = invoice.sender ? invoice.sender.name : (invoice.senderName || '');
|
||||||
let amount = invoice.totalAmount || invoice.amount || 0;
|
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>';
|
invoice_table += '</tbody></table></div>';
|
||||||
|
|
||||||
|
// Информация о количестве найденных инвойсов
|
||||||
|
let info_message = '<div class="alert alert-info">' +
|
||||||
|
__('Найдено новых счетов-фактур: ') + invoices.length +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
// Создаем диалог
|
// Создаем диалог
|
||||||
var d = new frappe.ui.Dialog({
|
var d = new frappe.ui.Dialog({
|
||||||
title: __('Выбор счета-фактуры'),
|
title: __('Выбор счета-фактуры'),
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'info_html',
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
options: info_message
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'invoices_html',
|
fieldname: 'invoices_html',
|
||||||
fieldtype: 'HTML',
|
fieldtype: 'HTML',
|
||||||
|
|
@ -882,7 +1108,12 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
||||||
d.hide();
|
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_label: __('Отмена'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -899,23 +1130,51 @@ function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для загрузки выбранных инвойсов
|
// Функция для загрузки выбранных инвойсов с прогресс-баром и механизмом отмены
|
||||||
function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
function load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count = 0) {
|
||||||
if (invoice_ids.length === 0) {
|
// Если все инвойсы обработаны или был запрос на отмену
|
||||||
// Если все инвойсы обработаны, обновляем документ
|
if (invoice_ids.length === 0 || cancel_loading) {
|
||||||
|
// Если был запрос на отмену, показываем соответствующее сообщение
|
||||||
|
if (cancel_loading) {
|
||||||
|
set_loading_success(
|
||||||
|
__('Загрузка отменена'),
|
||||||
|
__('Загружено ' + processed_count + ' инвойсов из ' + (processed_count + invoice_ids.length)),
|
||||||
|
function() {
|
||||||
if (frm) {
|
if (frm) {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
} else {
|
} else {
|
||||||
cur_list.refresh();
|
cur_list.refresh();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Все успешно загружено
|
||||||
|
set_loading_success(
|
||||||
|
__('Загрузка завершена'),
|
||||||
|
__('Загружено ' + processed_count + ' инвойсов'),
|
||||||
|
function() {
|
||||||
|
if (frm) {
|
||||||
|
frm.reload_doc();
|
||||||
|
} else {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Берем первый ID из списка
|
// Берем первый ID из списка
|
||||||
var invoice_id = invoice_ids.shift();
|
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({
|
frappe.call({
|
||||||
|
|
@ -925,13 +1184,24 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||||
'invoice_id': invoice_id
|
'invoice_id': invoice_id
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
// Закрываем диалог загрузки
|
if (cancel_loading) {
|
||||||
hide_loading_dialog();
|
// Если был запрос на отмену во время запроса, прекращаем загрузку
|
||||||
|
load_selected_invoices(frm, [], token, warehouse, processed_count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (r.message && !r.message.error) {
|
if (r.message && !r.message.error) {
|
||||||
// Получаем дату создания инвойса
|
// Получаем дату создания инвойса
|
||||||
let creationDate = r.message.creationDate || r.message.date || frappe.datetime.nowdate();
|
let creationDate = r.message.creationDate || r.message.date || frappe.datetime.nowdate();
|
||||||
let scheduleDate = moment(creationDate).format('YYYY-MM-DD');
|
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({
|
frappe.call({
|
||||||
|
|
@ -943,23 +1213,80 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||||
'warehouse': warehouse // Передаем склад в API
|
'warehouse': warehouse // Передаем склад в API
|
||||||
},
|
},
|
||||||
callback: function(import_r) {
|
callback: function(import_r) {
|
||||||
|
if (cancel_loading) {
|
||||||
|
// Если был запрос на отмену во время запроса, прекращаем загрузку
|
||||||
|
load_selected_invoices(frm, [], token, warehouse, processed_count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (import_r.message && import_r.message.success) {
|
if (import_r.message && import_r.message.success) {
|
||||||
// Показываем уведомление об успешном импорте
|
// Показываем уведомление об успешном импорте
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Инвойс успешно импортирован: ' + r.message.serialNumber),
|
message: __('Инвойс успешно импортирован: ') + serialNumber,
|
||||||
indicator: 'green'
|
indicator: 'green'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
|
// Получаем имя созданного 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() {
|
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;
|
||||||
}
|
}
|
||||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||||
}, 1000);
|
}, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} 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)) {
|
||||||
// Если есть несопоставленные элементы или контрагенты
|
// Если есть несопоставленные элементы или контрагенты
|
||||||
|
hide_loading_dialog();
|
||||||
|
|
||||||
if (import_r.message.unmatched_items) {
|
if (import_r.message.unmatched_items) {
|
||||||
show_unmatched_items_dialog(r.message, import_r.message.unmatched_items);
|
show_unmatched_items_dialog(r.message, import_r.message.unmatched_items);
|
||||||
} else if (import_r.message.unmatched_parties) {
|
} else if (import_r.message.unmatched_parties) {
|
||||||
|
|
@ -973,21 +1300,22 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||||
message: __('Для продолжения необходимо создать недостающие сопоставления')
|
message: __('Для продолжения необходимо создать недостающие сопоставления')
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint({
|
frappe.show_alert({
|
||||||
title: __('Ошибка'),
|
message: __('Ошибка импорта: ') + serialNumber,
|
||||||
indicator: 'red',
|
indicator: 'red'
|
||||||
message: import_r.message ? import_r.message.message : __('Ошибка импорта данных счета-фактуры')
|
}, 3);
|
||||||
});
|
|
||||||
|
|
||||||
// Переходим к следующему инвойсу
|
// Переходим к следующему инвойсу, не увеличивая счетчик успешно обработанных
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||||
}, 1000);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (r.message && r.message.error === 'unauthorized') {
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
// Если ошибка авторизации, предлагаем авторизоваться заново
|
// Если ошибка авторизации, предлагаем авторизоваться заново
|
||||||
|
hide_loading_dialog();
|
||||||
|
|
||||||
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() {
|
||||||
|
|
@ -998,8 +1326,15 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||||
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) {
|
||||||
|
// Показываем диалог с прогрессом снова
|
||||||
|
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 {
|
} else {
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Ошибка'),
|
title: __('Ошибка'),
|
||||||
|
|
@ -1020,16 +1355,15 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint({
|
frappe.show_alert({
|
||||||
title: __('Ошибка'),
|
message: __('Ошибка загрузки данных инвойса'),
|
||||||
indicator: 'red',
|
indicator: 'red'
|
||||||
message: r.message ? r.message.message : __('Ошибка загрузки данных счета-фактуры')
|
}, 3);
|
||||||
});
|
|
||||||
|
|
||||||
// Переходим к следующему инвойсу
|
// Переходим к следующему инвойсу
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
load_selected_invoices(frm, invoice_ids, token, warehouse, processed_count);
|
||||||
}, 1000);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,23 @@ app_email = "info@jeyerp.az"
|
||||||
app_license = "unlicense"
|
app_license = "unlicense"
|
||||||
|
|
||||||
doctype_js = {
|
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 = {
|
doctype_list_js = {
|
||||||
"E-Taxes Item": "client/e_taxes_items_list.js",
|
"E-Taxes Item": "client/e_taxes_items_list.js",
|
||||||
"E-Taxes Parties": "client/e_taxes_parties_list.js",
|
"E-Taxes Parties": "client/e_taxes_parties_list.js",
|
||||||
"Purchase Order": "client/purchase_order.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
|
# Apps
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Item Name",
|
"label": "Item Name",
|
||||||
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
|
|
@ -31,37 +32,43 @@
|
||||||
"fieldname": "etaxes_item_code",
|
"fieldname": "etaxes_item_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Item Code",
|
"label": "Item Code",
|
||||||
|
"read_only": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "etaxes_unit",
|
"fieldname": "etaxes_unit",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Unit of Measure"
|
"label": "Unit of Measure",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "etaxes_price",
|
"fieldname": "etaxes_price",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Price",
|
"label": "Price",
|
||||||
"options": "AZN"
|
"options": "AZN",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Source invoice number",
|
"description": "Source invoice number",
|
||||||
"fieldname": "source_invoice",
|
"fieldname": "source_invoice",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Source"
|
"label": "Source",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "New",
|
"default": "New",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "New\nMapped\nProcessing"
|
"options": "New\nMapped\nProcessing",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "mapped_item",
|
"fieldname": "mapped_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Mapped Item",
|
"label": "Mapped Item",
|
||||||
"options": "Item"
|
"options": "Item",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_creation_section",
|
"fieldname": "item_creation_section",
|
||||||
|
|
@ -72,24 +79,27 @@
|
||||||
"fieldname": "item_group",
|
"fieldname": "item_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Item Group",
|
"label": "Item Group",
|
||||||
"options": "Item Group"
|
"options": "Item Group",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "uom",
|
"fieldname": "uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Unit of Measure",
|
"label": "Unit of Measure",
|
||||||
"options": "UOM"
|
"options": "UOM",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_tax_template",
|
"fieldname": "item_tax_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Tax Template",
|
"label": "Tax Template",
|
||||||
"options": "Item Tax Template"
|
"options": "Item Tax Template",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-06 19:24:47.211911",
|
"modified": "2025-05-15 14:25:13.578790",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Invoice Az",
|
"module": "Invoice Az",
|
||||||
"name": "E-Taxes Item",
|
"name": "E-Taxes Item",
|
||||||
|
|
|
||||||
|
|
@ -24,51 +24,59 @@
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Party Name",
|
"label": "Party Name",
|
||||||
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "etaxes_tax_id",
|
"fieldname": "etaxes_tax_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Tax ID"
|
"label": "Tax ID",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Sender",
|
"default": "Sender",
|
||||||
"fieldname": "etaxes_party_type",
|
"fieldname": "etaxes_party_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "E-Taxes Type",
|
"label": "E-Taxes Type",
|
||||||
"options": "Sender\nReceiver"
|
"options": "Sender\nReceiver",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "etaxes_address",
|
"fieldname": "etaxes_address",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Address"
|
"label": "Address",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Source invoice number",
|
"description": "Source invoice number",
|
||||||
"fieldname": "source_invoice",
|
"fieldname": "source_invoice",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Source"
|
"label": "Source",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "New",
|
"default": "New",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "New\nMapped\nProcessing"
|
"options": "New\nMapped\nProcessing",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Supplier",
|
"default": "Supplier",
|
||||||
"fieldname": "erp_party_type",
|
"fieldname": "erp_party_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "System Party Type",
|
"label": "System Party Type",
|
||||||
"options": "Supplier\nCustomer"
|
"options": "Supplier\nCustomer",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "mapped_party",
|
"fieldname": "mapped_party",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"label": "Mapped Party",
|
"label": "Mapped Party",
|
||||||
"options": "erp_party_type"
|
"options": "erp_party_type",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_creation_section",
|
"fieldname": "party_creation_section",
|
||||||
|
|
@ -79,7 +87,8 @@
|
||||||
"fieldname": "party_group",
|
"fieldname": "party_group",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"label": "Party Group",
|
"label": "Party Group",
|
||||||
"options": "party_group_type"
|
"options": "party_group_type",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Supplier Group",
|
"default": "Supplier Group",
|
||||||
|
|
@ -87,18 +96,20 @@
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Party Group Type",
|
"label": "Party Group Type",
|
||||||
"options": "Supplier Group\nCustomer Group"
|
"options": "Supplier Group\nCustomer Group",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "payment_terms",
|
"fieldname": "payment_terms",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Payment Terms",
|
"label": "Payment Terms",
|
||||||
"options": "Payment Terms Template"
|
"options": "Payment Terms Template",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-09 15:46:11.179417",
|
"modified": "2025-05-15 14:25:54.517365",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Invoice Az",
|
"module": "Invoice Az",
|
||||||
"name": "E-Taxes Parties",
|
"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) {
|
r.message.items.forEach(function(item) {
|
||||||
let row = frm.add_child('item_mappings');
|
let row = frm.add_child('item_mappings');
|
||||||
// ИСПРАВЛЕНИЕ: Используем ID документа (name) вместо отображаемого имени
|
|
||||||
|
// Используем ID документа (name) вместо отображаемого имени
|
||||||
row.etaxes_item_name = item.name; // Должен быть ID документа
|
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';
|
row.mapping_type = 'Manual';
|
||||||
});
|
});
|
||||||
frm.refresh_field('item_mappings');
|
frm.refresh_field('item_mappings');
|
||||||
|
|
@ -329,9 +339,29 @@ frappe.ui.form.on('E-Taxes Settings', {
|
||||||
// Добавляем несопоставленных контрагентов в таблицу
|
// Добавляем несопоставленных контрагентов в таблицу
|
||||||
r.message.parties.forEach(function(party) {
|
r.message.parties.forEach(function(party) {
|
||||||
let row = frm.add_child('party_mappings');
|
let row = frm.add_child('party_mappings');
|
||||||
|
|
||||||
|
// Основные поля
|
||||||
row.etaxes_party_name = party.etaxes_party_name;
|
row.etaxes_party_name = party.etaxes_party_name;
|
||||||
row.etaxes_tax_id = party.etaxes_tax_id;
|
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';
|
row.mapping_type = 'Manual';
|
||||||
});
|
});
|
||||||
frm.refresh_field('party_mappings');
|
frm.refresh_field('party_mappings');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue