added create if not mapped
This commit is contained in:
parent
c037dfe45c
commit
f1ae840d88
|
|
@ -82,6 +82,7 @@ VATETaxes.utils = {
|
|||
humanizeErrorType: function(errorType) {
|
||||
const errorTypeMap = {
|
||||
'customer_not_found': __('Customer Not Found'),
|
||||
'customer_creation_failed': __('Customer Creation Failed'),
|
||||
'account_not_found': __('Account Not Found'),
|
||||
'mapping_not_found': __('VAT Account Mapping Not Found'),
|
||||
'import_error': __('Import Error'),
|
||||
|
|
@ -892,13 +893,6 @@ VATETaxes.import = {
|
|||
options: 'Company',
|
||||
reqd: true,
|
||||
default: frappe.defaults.get_user_default('Company')
|
||||
},
|
||||
{
|
||||
fieldname: 'create_as_draft',
|
||||
fieldtype: 'Check',
|
||||
label: __('Create as Draft'),
|
||||
default: 0,
|
||||
description: __('If checked, Journal Entries will be created in Draft status instead of being submitted automatically')
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Search'),
|
||||
|
|
@ -934,7 +928,7 @@ VATETaxes.import = {
|
|||
const createAsDraft = values.create_as_draft || 0;
|
||||
|
||||
d.hide();
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, values.company, [], 0, createAsDraft);
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, values.company, [], 0, createAsDraft, 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -943,7 +937,7 @@ VATETaxes.import = {
|
|||
},
|
||||
|
||||
// Загрузить операции с фильтрацией дубликатов
|
||||
loadOperations: function(fromDate, toDate, company, accumulatedOperations = [], offset = 0, createAsDraft = 0) {
|
||||
loadOperations: function(fromDate, toDate, company, accumulatedOperations = [], offset = 0, createAsDraft = 0, autoCreateCustomers = 0) {
|
||||
VATETaxes.cancelLoading = false;
|
||||
$(document).off('progress-cancel.vat_import');
|
||||
|
||||
|
|
@ -990,7 +984,7 @@ VATETaxes.import = {
|
|||
|
||||
if (hasMore && currentOperations.length > 0) {
|
||||
const newOffset = offset + currentOperations.length;
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, company, allOperations, newOffset, createAsDraft);
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, company, allOperations, newOffset, createAsDraft, autoCreateCustomers);
|
||||
} else {
|
||||
if (allOperations.length > 0) {
|
||||
frappe.show_alert({
|
||||
|
|
@ -998,7 +992,7 @@ VATETaxes.import = {
|
|||
indicator: 'blue'
|
||||
}, 3);
|
||||
|
||||
VATETaxes.import.filterDuplicates(allOperations, mainToken, company, createAsDraft);
|
||||
VATETaxes.import.filterDuplicates(allOperations, mainToken, company, createAsDraft, autoCreateCustomers);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Information'),
|
||||
|
|
@ -1012,7 +1006,7 @@ VATETaxes.import = {
|
|||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
VATETaxes.auth.startProcess(function() {
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, company, [], 0, createAsDraft);
|
||||
VATETaxes.import.loadOperations(fromDate, toDate, company, [], 0, createAsDraft, autoCreateCustomers);
|
||||
});
|
||||
},
|
||||
function() {
|
||||
|
|
@ -1050,7 +1044,7 @@ VATETaxes.import = {
|
|||
},
|
||||
|
||||
// Фильтрация дубликатов
|
||||
filterDuplicates: function(allOperations, token, company, createAsDraft = 0) {
|
||||
filterDuplicates: function(allOperations, token, company, createAsDraft = 0, autoCreateCustomers = 0) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get_list',
|
||||
args: {
|
||||
|
|
@ -1085,7 +1079,7 @@ VATETaxes.import = {
|
|||
}
|
||||
|
||||
if (filteredOperations.length > 0) {
|
||||
VATETaxes.import.showOperationSelection(filteredOperations, token, company, createAsDraft);
|
||||
VATETaxes.import.showOperationSelection(filteredOperations, token, company, createAsDraft, autoCreateCustomers);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Information'),
|
||||
|
|
@ -1105,7 +1099,7 @@ VATETaxes.import = {
|
|||
},
|
||||
error: function(err) {
|
||||
console.error("Error fetching VAT operations:", err);
|
||||
VATETaxes.import.showOperationSelection(allOperations, token, company, createAsDraft);
|
||||
VATETaxes.import.showOperationSelection(allOperations, token, company, createAsDraft, autoCreateCustomers);
|
||||
frappe.show_alert({
|
||||
message: __('Failed to check for duplicates. Showing all operations.'),
|
||||
indicator: 'orange'
|
||||
|
|
@ -1170,6 +1164,30 @@ VATETaxes.import = {
|
|||
fieldtype: 'HTML',
|
||||
options: infoMessage
|
||||
},
|
||||
{
|
||||
fieldname: 'settings_section',
|
||||
fieldtype: 'Section Break',
|
||||
label: __('Import Settings')
|
||||
},
|
||||
{
|
||||
fieldname: 'create_as_draft',
|
||||
fieldtype: 'Check',
|
||||
label: __('Create as Draft'),
|
||||
default: createAsDraft,
|
||||
description: __('If checked, Journal Entries will be created in Draft status instead of being submitted automatically')
|
||||
},
|
||||
{
|
||||
fieldname: 'auto_create_customers',
|
||||
fieldtype: 'Check',
|
||||
label: __('Auto-create missing customers'),
|
||||
default: 0,
|
||||
description: __('If checked, will automatically create Customer records and mappings for TINs not found in the system')
|
||||
},
|
||||
{
|
||||
fieldname: 'operations_section',
|
||||
fieldtype: 'Section Break',
|
||||
label: __('Operations')
|
||||
},
|
||||
{
|
||||
fieldname: 'operations_html',
|
||||
fieldtype: 'HTML',
|
||||
|
|
@ -1198,6 +1216,11 @@ VATETaxes.import = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Получить значения настроек из диалога
|
||||
const values = d.get_values();
|
||||
const createAsDraftValue = values.create_as_draft || 0;
|
||||
const autoCreateCustomers = values.auto_create_customers || 0;
|
||||
|
||||
// Найти полные данные операций
|
||||
const selectedOperations = [];
|
||||
selectedOperationIds.forEach(function(id) {
|
||||
|
|
@ -1209,7 +1232,7 @@ VATETaxes.import = {
|
|||
|
||||
d.hide();
|
||||
VATETaxes.cancelLoading = false;
|
||||
VATETaxes.import.loadSelectedOperations(selectedOperations, token, company, 0, 0, createAsDraft);
|
||||
VATETaxes.import.loadSelectedOperations(selectedOperations, token, company, 0, 0, createAsDraftValue, autoCreateCustomers);
|
||||
},
|
||||
secondary_action_label: __('Cancel'),
|
||||
secondary_action: function() {
|
||||
|
|
@ -1237,7 +1260,7 @@ VATETaxes.import = {
|
|||
},
|
||||
|
||||
// Загрузка выбранных операций
|
||||
loadSelectedOperations: function(operations, token, company, processedCount = 0, currentIndex = 0, createAsDraft = 0) {
|
||||
loadSelectedOperations: function(operations, token, company, processedCount = 0, currentIndex = 0, createAsDraft = 0, autoCreateCustomers = 0) {
|
||||
if (processedCount === 0 && currentIndex === 0) {
|
||||
VATETaxes.loadingErrors = [];
|
||||
window.totalOperationsToProcess = operations.length;
|
||||
|
|
@ -1271,7 +1294,7 @@ VATETaxes.import = {
|
|||
if (VATETaxes.cancelLoading) {
|
||||
operations = [];
|
||||
frappe.hide_progress();
|
||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft);
|
||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft, autoCreateCustomers);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1299,11 +1322,12 @@ VATETaxes.import = {
|
|||
args: {
|
||||
'operations_data': JSON.stringify([operation]),
|
||||
'company': company,
|
||||
'create_as_draft': createAsDraft
|
||||
'create_as_draft': createAsDraft,
|
||||
'auto_create_customers': autoCreateCustomers
|
||||
},
|
||||
callback: function(r) {
|
||||
VATETaxes.import._handleImportResult(r, operation,
|
||||
operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft);
|
||||
operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft, autoCreateCustomers);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
const errorMessage = 'Network error during import: ' + (error || 'Unknown network error');
|
||||
|
|
@ -1312,13 +1336,13 @@ VATETaxes.import = {
|
|||
xhr_response: xhr.responseText
|
||||
});
|
||||
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft);
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft, autoCreateCustomers);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Обработка результата импорта
|
||||
_handleImportResult: function(importR, operation, operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft = 0) {
|
||||
_handleImportResult: function(importR, operation, operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft = 0, autoCreateCustomers = 0) {
|
||||
if (importR.message && importR.message.success) {
|
||||
if (importR.message.imported > 0) {
|
||||
// НЕ показываем индивидуальные алерты во время массового импорта
|
||||
|
|
@ -1330,19 +1354,19 @@ VATETaxes.import = {
|
|||
VATETaxes.errors.add(operation, error.error_type || 'Import Error', error.message, error);
|
||||
}
|
||||
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft);
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft, autoCreateCustomers);
|
||||
} else {
|
||||
const errorMessage = importR.message ? importR.message.message : 'Unknown import error';
|
||||
VATETaxes.errors.add(operation, 'Import Error', errorMessage, {
|
||||
server_response: importR.message
|
||||
});
|
||||
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft);
|
||||
VATETaxes.import._continueOrFinish(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft, autoCreateCustomers);
|
||||
}
|
||||
},
|
||||
|
||||
// Продолжить или завершить обработку
|
||||
_continueOrFinish: function(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft = 0) {
|
||||
_continueOrFinish: function(operations, token, company, processedCount, currentIndex, isLastOperation, createAsDraft = 0, autoCreateCustomers = 0) {
|
||||
if (isLastOperation) {
|
||||
$(document).off('progress-cancel.loading_operations');
|
||||
|
||||
|
|
@ -1372,7 +1396,7 @@ VATETaxes.import = {
|
|||
frappe.hide_progress();
|
||||
}
|
||||
|
||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft);
|
||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft, autoCreateCustomers);
|
||||
}, VATETaxes.PROGRESS_UPDATE_DELAY);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -186,6 +186,29 @@ def get_vat_operations(token, date_from, date_to, max_count=50, offset=0):
|
|||
return {"error": "unknown_error", "message": "An unknown error occurred, please try again."}
|
||||
|
||||
|
||||
def normalize_customer_name(name):
|
||||
"""
|
||||
Нормализует имя клиента для поиска
|
||||
Использует normalize_string() из sales_api.py
|
||||
|
||||
Args:
|
||||
name (str): Имя клиента
|
||||
|
||||
Returns:
|
||||
str: Нормализованное имя
|
||||
"""
|
||||
if not name:
|
||||
return ''
|
||||
|
||||
try:
|
||||
from invoice_az.sales_api import normalize_string
|
||||
return normalize_string(name, consider_azeri=True)
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error normalizing customer name '{name}': {str(e)}", "VAT Customer Normalize Error")
|
||||
# Fallback to simple lowercase if normalize_string fails
|
||||
return name.lower().strip()
|
||||
|
||||
|
||||
def find_customer_by_tin(tin):
|
||||
"""
|
||||
Поиск клиента по TIN (напрямую в Customer.tax_id)
|
||||
|
|
@ -224,6 +247,248 @@ def find_customer_by_tin(tin):
|
|||
return None
|
||||
|
||||
|
||||
def find_or_create_customer_from_vat_operation(tin, name, settings_doc, auto_create=False):
|
||||
"""
|
||||
Найти или создать клиента из VAT операции с автосозданием маппингов
|
||||
|
||||
Логика поиска (ПРАВИЛЬНЫЙ ПРИОРИТЕТ):
|
||||
0. Проверка маппингов (НАИВЫСШИЙ ПРИОРИТЕТ) - что указано в маппинге, то и используется
|
||||
- Если найден маппинг с заполненным erp_customer → возвращаем его
|
||||
- Если найден маппинг с пустым erp_customer → запоминаем и продолжаем поиск
|
||||
1. Если auto_create=False → return None
|
||||
2. Поиск Customer в базе (только если маппинг пустой или отсутствует):
|
||||
- По tax_id
|
||||
- По точному имени
|
||||
- По нормализованному имени
|
||||
- Если найден → создать/обновить маппинг
|
||||
3. Создание нового Customer + создание/обновление маппинга
|
||||
|
||||
Args:
|
||||
tin (str): TIN клиента
|
||||
name (str): Имя клиента из операции
|
||||
settings_doc (Document): Объект E-Taxes Settings
|
||||
auto_create (bool): Разрешить автосоздание
|
||||
|
||||
Returns:
|
||||
str: Имя клиента или None
|
||||
"""
|
||||
if not tin:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Шаг 0: НАИВЫСШИЙ ПРИОРИТЕТ - Проверка маппингов
|
||||
existing_mapping_row = None
|
||||
if settings_doc and hasattr(settings_doc, 'customer_mappings'):
|
||||
for mapping in settings_doc.customer_mappings:
|
||||
if mapping.etaxes_tax_id == tin:
|
||||
if mapping.erp_customer:
|
||||
# Маппинг с заполненным erp_customer - это источник истины!
|
||||
return mapping.erp_customer
|
||||
else:
|
||||
# Маппинг существует, но erp_customer пустой - запоминаем для обновления
|
||||
existing_mapping_row = mapping
|
||||
break
|
||||
|
||||
# Если auto_create=False, завершаем поиск
|
||||
if not auto_create:
|
||||
return None
|
||||
|
||||
# Шаг 1: Поиск Customer по tax_id в базе
|
||||
customer_name = frappe.db.get_value('Customer', {'tax_id': tin}, 'name')
|
||||
if customer_name:
|
||||
# Найден Customer - создаем/обновляем маппинг
|
||||
_create_or_update_etaxes_customer_and_mapping(tin, name, customer_name, settings_doc, existing_mapping_row)
|
||||
return customer_name
|
||||
|
||||
# Шаг 2: Поиск Customer по точному имени
|
||||
if name:
|
||||
customer_name = frappe.db.get_value('Customer', {'customer_name': name}, 'name')
|
||||
if customer_name:
|
||||
# Обновляем tax_id если нужно (с защитой от конфликта)
|
||||
try:
|
||||
customer_doc = frappe.get_doc('Customer', customer_name)
|
||||
if not customer_doc.tax_id or customer_doc.tax_id != tin:
|
||||
# Проверяем нет ли уже другого Customer с таким tax_id
|
||||
existing_customer_with_tin = frappe.db.get_value('Customer', {'tax_id': tin}, 'name')
|
||||
if not existing_customer_with_tin or existing_customer_with_tin == customer_name:
|
||||
customer_doc.tax_id = tin
|
||||
customer_doc.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
# Игнорируем ошибки обновления tax_id
|
||||
pass
|
||||
|
||||
# Создаем/обновляем маппинг
|
||||
_create_or_update_etaxes_customer_and_mapping(tin, name, customer_name, settings_doc, existing_mapping_row)
|
||||
return customer_name
|
||||
|
||||
# Шаг 3: Поиск по нормализованному имени
|
||||
if name:
|
||||
normalized_search_name = normalize_customer_name(name)
|
||||
if normalized_search_name:
|
||||
all_customers = frappe.get_all('Customer', fields=['name', 'customer_name'])
|
||||
for customer in all_customers:
|
||||
customer_normalized = normalize_customer_name(customer.get('customer_name', ''))
|
||||
if customer_normalized == normalized_search_name:
|
||||
# Обновляем tax_id если нужно (с защитой от конфликта)
|
||||
try:
|
||||
customer_doc = frappe.get_doc('Customer', customer['name'])
|
||||
if not customer_doc.tax_id or customer_doc.tax_id != tin:
|
||||
# Проверяем нет ли уже другого Customer с таким tax_id
|
||||
existing_customer_with_tin = frappe.db.get_value('Customer', {'tax_id': tin}, 'name')
|
||||
if not existing_customer_with_tin or existing_customer_with_tin == customer['name']:
|
||||
customer_doc.tax_id = tin
|
||||
customer_doc.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
# Игнорируем ошибки обновления tax_id
|
||||
pass
|
||||
|
||||
# Создаем/обновляем маппинг
|
||||
_create_or_update_etaxes_customer_and_mapping(tin, name, customer['name'], settings_doc, existing_mapping_row)
|
||||
return customer['name']
|
||||
|
||||
# Шаг 4: Создание нового Customer
|
||||
# Получаем default customer group
|
||||
default_customer_group = settings_doc.default_customer_group if settings_doc else None
|
||||
if not default_customer_group or not frappe.db.exists('Customer Group', default_customer_group):
|
||||
default_customer_group = 'All Customer Groups'
|
||||
|
||||
# Создаем Customer (с защитой от race condition)
|
||||
try:
|
||||
customer_doc = frappe.new_doc('Customer')
|
||||
customer_doc.customer_name = name or f"Customer-{tin}"
|
||||
customer_doc.customer_type = 'Company'
|
||||
customer_doc.customer_group = default_customer_group
|
||||
customer_doc.territory = 'All Territories'
|
||||
customer_doc.tax_id = tin
|
||||
|
||||
# Payment terms если есть
|
||||
if settings_doc and hasattr(settings_doc, 'default_payment_terms') and settings_doc.default_payment_terms:
|
||||
customer_doc.payment_terms = settings_doc.default_payment_terms
|
||||
|
||||
customer_doc.insert(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
# Создаем/обновляем маппинг
|
||||
_create_or_update_etaxes_customer_and_mapping(tin, name, customer_doc.name, settings_doc, existing_mapping_row)
|
||||
|
||||
return customer_doc.name
|
||||
|
||||
except frappe.exceptions.DuplicateEntryError:
|
||||
# Race condition: кто-то уже создал Customer с таким именем или tax_id
|
||||
# Попробуем найти снова по tax_id
|
||||
customer_name = frappe.db.get_value('Customer', {'tax_id': tin}, 'name')
|
||||
if customer_name:
|
||||
_create_or_update_etaxes_customer_and_mapping(tin, name, customer_name, settings_doc, existing_mapping_row)
|
||||
return customer_name
|
||||
# Если не нашли - возвращаем None
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(
|
||||
f"Error finding/creating customer for TIN {tin}, name {name}: {str(e)}\n{frappe.get_traceback()}",
|
||||
"VAT Customer Auto-Create Error"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _create_or_update_etaxes_customer_and_mapping(tin, etaxes_name, erp_customer, settings_doc, existing_mapping_row=None):
|
||||
"""
|
||||
Вспомогательная функция для создания/обновления E-Taxes Customers и маппинга
|
||||
Работает ТИХО - без сообщений пользователю
|
||||
|
||||
Args:
|
||||
tin (str): TIN клиента
|
||||
etaxes_name (str): Имя из e-taxes
|
||||
erp_customer (str): Имя Customer в ERP
|
||||
settings_doc (Document): Объект E-Taxes Settings
|
||||
existing_mapping_row (object): Существующая строка маппинга с пустым erp_customer (если есть)
|
||||
"""
|
||||
# Сначала обновляем маппинг (это главное!)
|
||||
try:
|
||||
if settings_doc:
|
||||
if existing_mapping_row:
|
||||
# Используем прямое обновление child table через SQL
|
||||
default_customer_group = settings_doc.default_customer_group if settings_doc.default_customer_group else 'All Customer Groups'
|
||||
|
||||
# Обновляем через frappe.db.sql напрямую
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabE-Taxes Customer Mappings`
|
||||
SET erp_customer = %s,
|
||||
customer_group = COALESCE(NULLIF(customer_group, ''), %s),
|
||||
territory = COALESCE(NULLIF(territory, ''), 'All Territories'),
|
||||
mapping_type = COALESCE(NULLIF(mapping_type, ''), 'Automatic')
|
||||
WHERE parent = %s
|
||||
AND etaxes_tax_id = %s
|
||||
""", (erp_customer, default_customer_group, settings_doc.name, tin))
|
||||
frappe.db.commit()
|
||||
else:
|
||||
# Проверяем, нет ли уже маппинга с таким TIN (на всякий случай)
|
||||
mapping_exists = False
|
||||
if hasattr(settings_doc, 'customer_mappings'):
|
||||
for mapping in settings_doc.customer_mappings:
|
||||
if mapping.etaxes_tax_id == tin:
|
||||
mapping_exists = True
|
||||
break
|
||||
|
||||
if not mapping_exists:
|
||||
# Создаем новую строку маппинга
|
||||
settings_doc.append('customer_mappings', {
|
||||
'etaxes_customer_name': etaxes_name or f"Customer-{tin}",
|
||||
'etaxes_tax_id': tin,
|
||||
'erp_customer': erp_customer,
|
||||
'customer_group': settings_doc.default_customer_group if settings_doc.default_customer_group else 'All Customer Groups',
|
||||
'territory': 'All Territories',
|
||||
'mapping_type': 'Automatic'
|
||||
})
|
||||
settings_doc.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
# Тихая обработка ошибок маппинга - только в лог
|
||||
frappe.log_error(
|
||||
f"Error updating mapping for TIN {tin}: {str(e)}\n{frappe.get_traceback()}",
|
||||
"VAT Mapping Update Error"
|
||||
)
|
||||
|
||||
# Потом пытаемся создать/обновить E-Taxes Customers (если упадет - не страшно)
|
||||
try:
|
||||
# Проверяем существование по полям, а не по name (autoname может генерировать другой формат!)
|
||||
existing = frappe.db.get_value('E-Taxes Customers',
|
||||
{'etaxes_tax_id': tin},
|
||||
'name')
|
||||
|
||||
if existing:
|
||||
try:
|
||||
etaxes_customer_doc = frappe.get_doc('E-Taxes Customers', existing)
|
||||
if not etaxes_customer_doc.mapped_customer:
|
||||
etaxes_customer_doc.mapped_customer = erp_customer
|
||||
etaxes_customer_doc.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
# Игнорируем ошибки при обновлении существующего
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
etaxes_customer_doc = frappe.new_doc('E-Taxes Customers')
|
||||
etaxes_customer_doc.etaxes_party_name = etaxes_name or f"Customer-{tin}"
|
||||
etaxes_customer_doc.etaxes_tax_id = tin
|
||||
etaxes_customer_doc.etaxes_party_type = 'Receiver'
|
||||
etaxes_customer_doc.status = 'New'
|
||||
etaxes_customer_doc.mapped_customer = erp_customer
|
||||
etaxes_customer_doc.insert(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
except frappe.exceptions.DuplicateEntryError:
|
||||
# Игнорируем ошибку дубликата - кто-то уже создал
|
||||
pass
|
||||
except Exception:
|
||||
# Игнорируем любые другие ошибки создания E-Taxes Customers
|
||||
pass
|
||||
except Exception:
|
||||
# Игнорируем ошибки E-Taxes Customers полностью
|
||||
pass
|
||||
|
||||
|
||||
def find_account_by_number(account_number, company):
|
||||
"""
|
||||
Поиск счета по номеру счета
|
||||
|
|
@ -349,7 +614,7 @@ def get_accounts_for_operation_type(operation_type, company, expense_income_type
|
|||
return (None, None)
|
||||
|
||||
|
||||
def create_journal_entry_from_vat_operation(operation_data, company, create_as_draft=0):
|
||||
def create_journal_entry_from_vat_operation(operation_data, company, create_as_draft=0, auto_create_customers=0):
|
||||
"""
|
||||
Создание Journal Entry из VAT операции
|
||||
|
||||
|
|
@ -357,6 +622,7 @@ def create_journal_entry_from_vat_operation(operation_data, company, create_as_d
|
|||
operation_data (dict): Данные операции из API
|
||||
company (str): Название компании
|
||||
create_as_draft (int): 1 для создания в Draft, 0 для auto-submit
|
||||
auto_create_customers (int): 1 для автосоздания клиентов, 0 для отключения
|
||||
|
||||
Returns:
|
||||
dict: Результат создания с именем JE или ошибкой
|
||||
|
|
@ -434,17 +700,44 @@ def create_journal_entry_from_vat_operation(operation_data, company, create_as_d
|
|||
debit_needs_party = debit_account_type in party_required_types
|
||||
credit_needs_party = credit_account_type in party_required_types
|
||||
|
||||
# Получаем TIN и ищем клиента (если хотя бы один счет требует party)
|
||||
# Получаем TIN и ищем/создаем клиента (если хотя бы один счет требует party)
|
||||
tin = operation_data.get('tin', '').strip()
|
||||
customer_name_from_operation = operation_data.get('name', '')
|
||||
customer = None
|
||||
|
||||
if debit_needs_party or credit_needs_party:
|
||||
customer = find_customer_by_tin(tin)
|
||||
# Получаем настройки E-Taxes для маппингов
|
||||
settings_doc = None
|
||||
try:
|
||||
settings_name = frappe.db.get_value('E-Taxes Settings',
|
||||
{'is_default': 1, 'is_active': 1},
|
||||
'name')
|
||||
if not settings_name:
|
||||
settings_name = frappe.db.get_value('E-Taxes Settings',
|
||||
{'is_active': 1},
|
||||
'name')
|
||||
if settings_name:
|
||||
settings_doc = frappe.get_doc('E-Taxes Settings', settings_name)
|
||||
except Exception as e:
|
||||
frappe.logger().warning(f"[VAT] Could not load E-Taxes Settings: {str(e)}")
|
||||
|
||||
# Используем новую функцию для поиска/создания клиента
|
||||
customer = find_or_create_customer_from_vat_operation(
|
||||
tin=tin,
|
||||
name=customer_name_from_operation,
|
||||
settings_doc=settings_doc,
|
||||
auto_create=bool(auto_create_customers)
|
||||
)
|
||||
|
||||
if not customer:
|
||||
error_message = f'Customer not found for TIN: {tin}'
|
||||
if auto_create_customers:
|
||||
error_message = f'Could not find or create customer for TIN: {tin}'
|
||||
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Customer not found for TIN: {tin}',
|
||||
'error_type': 'customer_not_found',
|
||||
'message': error_message,
|
||||
'error_type': 'customer_creation_failed' if auto_create_customers else 'customer_not_found',
|
||||
'tin': tin
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +881,7 @@ def create_vat_operation_record(operation_data, journal_entry_name=None, status=
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_vat_operations(operations_data, company=None, create_as_draft=0):
|
||||
def import_vat_operations(operations_data, company=None, create_as_draft=0, auto_create_customers=0):
|
||||
"""
|
||||
Импорт VAT операций и создание Journal Entries
|
||||
|
||||
|
|
@ -596,6 +889,7 @@ def import_vat_operations(operations_data, company=None, create_as_draft=0):
|
|||
operations_data (str/list): JSON строка или список операций
|
||||
company (str): Название компании (опционально)
|
||||
create_as_draft (int): 1 для создания в Draft, 0 для auto-submit
|
||||
auto_create_customers (int): 1 для автосоздания клиентов, 0 для отключения
|
||||
|
||||
Returns:
|
||||
dict: Результат импорта с статистикой
|
||||
|
|
@ -613,6 +907,12 @@ def import_vat_operations(operations_data, company=None, create_as_draft=0):
|
|||
except (ValueError, TypeError):
|
||||
create_as_draft = 0
|
||||
|
||||
# Обработка auto_create_customers параметра
|
||||
try:
|
||||
auto_create_customers = int(auto_create_customers)
|
||||
except (ValueError, TypeError):
|
||||
auto_create_customers = 0
|
||||
|
||||
# Получаем компанию
|
||||
if not company:
|
||||
company = frappe.defaults.get_user_default('Company')
|
||||
|
|
@ -644,7 +944,7 @@ def import_vat_operations(operations_data, company=None, create_as_draft=0):
|
|||
continue
|
||||
|
||||
# Создаем Journal Entry
|
||||
je_result = create_journal_entry_from_vat_operation(operation, company, create_as_draft)
|
||||
je_result = create_journal_entry_from_vat_operation(operation, company, create_as_draft, auto_create_customers)
|
||||
|
||||
if je_result.get('success'):
|
||||
# Создаем запись отслеживания
|
||||
|
|
|
|||
Loading…
Reference in New Issue