Parties got separated to customers and suppliers
This commit is contained in:
parent
295dfb2267
commit
85f37a3f67
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
// Обновленные настройки списка E-Taxes Parties с прогресс баром
|
// Настройки списка E-Taxes Customers с прогресс баром
|
||||||
frappe.listview_settings['E-Taxes Parties'] = {
|
frappe.listview_settings['E-Taxes Customers'] = {
|
||||||
add_fields: ['status', 'mapped_party'],
|
add_fields: ['status', 'mapped_customer'],
|
||||||
|
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if (doc.status === 'Mapped') {
|
if (doc.status === 'Mapped') {
|
||||||
|
|
@ -13,48 +13,48 @@ frappe.listview_settings['E-Taxes Parties'] = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(listview) {
|
onload: function(listview) {
|
||||||
// Add button to load parties from E-Taxes
|
// Add button to load customers from E-Taxes
|
||||||
listview.page.add_menu_item(__('Load from E-Taxes'), function() {
|
listview.page.add_menu_item(__('Load from E-Taxes'), function() {
|
||||||
// Сначала проверяем актуальность токена перед загрузкой
|
// Сначала проверяем актуальность токена перед загрузкой
|
||||||
check_and_process_token_parties(function() {
|
check_and_process_token_customers(function() {
|
||||||
show_invoice_filter_dialog();
|
show_invoice_filter_dialog_customers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Глобальная переменная для хранения диалога загрузки
|
// Глобальная переменная для хранения диалога загрузки
|
||||||
var loading_dialog_parties = null;
|
var loading_dialog_customers = null;
|
||||||
|
|
||||||
// Очистка состояния при закрытии/перезагрузке страницы
|
// Очистка состояния при закрытии/перезагрузке страницы
|
||||||
$(window).on('beforeunload', function() {
|
$(window).on('beforeunload', function() {
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Очистка состояния при переходе на другую страницу
|
// Очистка состояния при переходе на другую страницу
|
||||||
$(document).on('page:before-change', function() {
|
$(document).on('page:before-change', function() {
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
});
|
});
|
||||||
|
|
||||||
// НОВАЯ ФУНКЦИЯ: Очистка состояния загрузки контрагентов
|
// НОВАЯ ФУНКЦИЯ: Очистка состояния загрузки customers
|
||||||
function cleanup_parties_loading_state() {
|
function cleanup_customers_loading_state() {
|
||||||
try {
|
try {
|
||||||
// Скрываем прогресс бар
|
// Скрываем прогресс бар
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
|
|
||||||
// Удаляем event listeners
|
// Удаляем event listeners
|
||||||
$(document).off('progress-cancel.loading_parties');
|
$(document).off('progress-cancel.loading_customers');
|
||||||
|
|
||||||
// Очищаем глобальные переменные
|
// Очищаем глобальные переменные
|
||||||
if (typeof window.cancelPartiesLoading !== 'undefined') {
|
if (typeof window.cancelCustomersLoading !== 'undefined') {
|
||||||
delete window.cancelPartiesLoading;
|
delete window.cancelCustomersLoading;
|
||||||
}
|
}
|
||||||
if (typeof window.totalPartiesToProcess !== 'undefined') {
|
if (typeof window.totalCustomersToProcess !== 'undefined') {
|
||||||
delete window.totalPartiesToProcess;
|
delete window.totalCustomersToProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ВАЖНО: Скрываем все возможные диалоги загрузки
|
// ВАЖНО: Скрываем все возможные диалоги загрузки
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
|
|
||||||
// Дополнительная очистка - удаляем все модальные окна, которые могут висеть
|
// Дополнительная очистка - удаляем все модальные окна, которые могут висеть
|
||||||
$('.modal-backdrop').remove();
|
$('.modal-backdrop').remove();
|
||||||
|
|
@ -63,9 +63,9 @@ function cleanup_parties_loading_state() {
|
||||||
// Очищаем любые блокировки интерфейса
|
// Очищаем любые блокировки интерфейса
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
|
|
||||||
console.log('Parties loading state cleaned up');
|
console.log('Customers loading state cleaned up');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error during parties cleanup:', e);
|
console.error('Error during customers cleanup:', e);
|
||||||
// В крайнем случае, принудительно разблокируем интерфейс
|
// В крайнем случае, принудительно разблокируем интерфейс
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
$('.modal-backdrop').remove();
|
$('.modal-backdrop').remove();
|
||||||
|
|
@ -74,21 +74,21 @@ function cleanup_parties_loading_state() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для отображения диалога загрузки с анимацией
|
// Функция для отображения диалога загрузки с анимацией
|
||||||
function show_loading_dialog_parties(title, message, submessage) {
|
function show_loading_dialog_customers(title, message, submessage) {
|
||||||
// Если диалог уже открыт, обновляем только сообщение
|
// Если диалог уже открыт, обновляем только сообщение
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
$('#loading_title_parties').text(title);
|
$('#loading_title_customers').text(title);
|
||||||
$('#loading_message_parties').text(message);
|
$('#loading_message_customers').text(message);
|
||||||
if (submessage) {
|
if (submessage) {
|
||||||
$('#loading_submessage_parties').text(submessage).show();
|
$('#loading_submessage_customers').text(submessage).show();
|
||||||
} else {
|
} else {
|
||||||
$('#loading_submessage_parties').hide();
|
$('#loading_submessage_customers').hide();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем диалог с анимацией загрузки
|
// Создаем диалог с анимацией загрузки
|
||||||
loading_dialog_parties = new frappe.ui.Dialog({
|
loading_dialog_customers = new frappe.ui.Dialog({
|
||||||
title: title,
|
title: title,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
|
|
@ -106,13 +106,13 @@ function show_loading_dialog_parties(title, message, submessage) {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h4 id="loading_title_parties">${title}</h4>
|
<h4 id="loading_title_customers">${title}</h4>
|
||||||
<p id="loading_message_parties">${message}</p>
|
<p id="loading_message_customers">${message}</p>
|
||||||
<p id="loading_submessage_parties" style="${submessage ? '' : 'display:none'}">${submessage || ''}</p>
|
<p id="loading_submessage_customers" style="${submessage ? '' : 'display:none'}">${submessage || ''}</p>
|
||||||
<!-- Блок для отображения кода верификации -->
|
<!-- Блок для отображения кода верификации -->
|
||||||
<div id="verification_code_container_parties" style="display:none; margin-top: 15px;">
|
<div id="verification_code_container_customers" style="display:none; margin-top: 15px;">
|
||||||
<div style="background: #f8f9fa; padding: 10px; display: inline-block; border-radius: 5px; font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #333; border: 1px solid #ddd;">
|
<div style="background: #f8f9fa; padding: 10px; display: inline-block; border-radius: 5px; font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #333; border: 1px solid #ddd;">
|
||||||
<span id="verification_code_parties">----</span>
|
<span id="verification_code_customers">----</span>
|
||||||
</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>
|
||||||
|
|
@ -123,63 +123,63 @@ function show_loading_dialog_parties(title, message, submessage) {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
loading_dialog_parties.show();
|
loading_dialog_customers.show();
|
||||||
loading_dialog_parties.$wrapper.find('.modal-dialog').css('max-width', '450px');
|
loading_dialog_customers.$wrapper.find('.modal-dialog').css('max-width', '450px');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕННАЯ функция для скрытия диалога загрузки
|
// ИСПРАВЛЕННАЯ функция для скрытия диалога загрузки
|
||||||
function hide_loading_dialog_parties() {
|
function hide_loading_dialog_customers() {
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
try {
|
try {
|
||||||
loading_dialog_parties.hide();
|
loading_dialog_customers.hide();
|
||||||
loading_dialog_parties = null;
|
loading_dialog_customers = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error hiding loading dialog parties:', e);
|
console.error('Error hiding loading dialog customers:', e);
|
||||||
loading_dialog_parties = null;
|
loading_dialog_customers = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для обновления сообщения в диалоге загрузки
|
// Функция для обновления сообщения в диалоге загрузки
|
||||||
function update_loading_message_parties(message, submessage) {
|
function update_loading_message_customers(message, submessage) {
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
$('#loading_message_parties').text(message);
|
$('#loading_message_customers').text(message);
|
||||||
if (submessage !== undefined) {
|
if (submessage !== undefined) {
|
||||||
$('#loading_submessage_parties').text(submessage);
|
$('#loading_submessage_customers').text(submessage);
|
||||||
$('#loading_submessage_parties').toggle(!!submessage);
|
$('#loading_submessage_customers').toggle(!!submessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для изменения статуса загрузки на успешный с автоматическим закрытием
|
// Функция для изменения статуса загрузки на успешный с автоматическим закрытием
|
||||||
function set_loading_success_parties(message, submessage, callback, delay = 2000) {
|
function set_loading_success_customers(message, submessage, callback, delay = 2000) {
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
// Меняем анимацию на галочку
|
// Меняем анимацию на галочку
|
||||||
loading_dialog_parties.$wrapper.find('.lds-dual-ring').html(`
|
loading_dialog_customers.$wrapper.find('.lds-dual-ring').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>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Обновляем сообщения
|
// Обновляем сообщения
|
||||||
$('#loading_message_parties').text(message);
|
$('#loading_message_customers').text(message);
|
||||||
if (submessage !== undefined) {
|
if (submessage !== undefined) {
|
||||||
$('#loading_submessage_parties').text(submessage);
|
$('#loading_submessage_customers').text(submessage);
|
||||||
$('#loading_submessage_parties').toggle(!!submessage);
|
$('#loading_submessage_customers').toggle(!!submessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скрываем код верификации при успешном завершении
|
// Скрываем код верификации при успешном завершении
|
||||||
$('#verification_code_container_parties').hide();
|
$('#verification_code_container_customers').hide();
|
||||||
|
|
||||||
// Удаляем все существующие кнопки действий
|
// Удаляем все существующие кнопки действий
|
||||||
loading_dialog_parties.set_primary_action(null);
|
loading_dialog_customers.set_primary_action(null);
|
||||||
loading_dialog_parties.set_secondary_action(null);
|
loading_dialog_customers.set_secondary_action(null);
|
||||||
|
|
||||||
// Автоматически закрываем через указанную задержку
|
// Автоматически закрываем через указанную задержку
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
loading_dialog_parties.hide();
|
loading_dialog_customers.hide();
|
||||||
loading_dialog_parties = null;
|
loading_dialog_customers = null;
|
||||||
}
|
}
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}, delay);
|
}, delay);
|
||||||
|
|
@ -190,36 +190,36 @@ function set_loading_success_parties(message, submessage, callback, delay = 2000
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для изменения статуса загрузки на ошибку
|
// Функция для изменения статуса загрузки на ошибку
|
||||||
function set_loading_error_parties(message, submessage, show_close_button = true) {
|
function set_loading_error_customers(message, submessage, show_close_button = true) {
|
||||||
if (loading_dialog_parties) {
|
if (loading_dialog_customers) {
|
||||||
// Меняем анимацию на крестик
|
// Меняем анимацию на крестик
|
||||||
loading_dialog_parties.$wrapper.find('.lds-dual-ring').html(`
|
loading_dialog_customers.$wrapper.find('.lds-dual-ring').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>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Обновляем сообщения
|
// Обновляем сообщения
|
||||||
$('#loading_message_parties').text(message);
|
$('#loading_message_customers').text(message);
|
||||||
if (submessage !== undefined) {
|
if (submessage !== undefined) {
|
||||||
$('#loading_submessage_parties').text(submessage);
|
$('#loading_submessage_customers').text(submessage);
|
||||||
$('#loading_submessage_parties').toggle(!!submessage);
|
$('#loading_submessage_customers').toggle(!!submessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скрываем код верификации при ошибке
|
// Скрываем код верификации при ошибке
|
||||||
$('#verification_code_container_parties').hide();
|
$('#verification_code_container_customers').hide();
|
||||||
|
|
||||||
// При необходимости добавляем кнопку закрытия
|
// При необходимости добавляем кнопку закрытия
|
||||||
if (show_close_button) {
|
if (show_close_button) {
|
||||||
loading_dialog_parties.set_primary_action(__('Close'), function() {
|
loading_dialog_customers.set_primary_action(__('Close'), function() {
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для проверки токена и обработки аутентификации при необходимости
|
// Функция для проверки токена и обработки аутентификации при необходимости
|
||||||
function check_and_process_token_parties(callback) {
|
function check_and_process_token_customers(callback) {
|
||||||
// Проверяем, доступен ли новый модуль ETaxes
|
// Проверяем, доступен ли новый модуль ETaxes
|
||||||
if (typeof ETaxes !== 'undefined' && ETaxes.auth) {
|
if (typeof ETaxes !== 'undefined' && ETaxes.auth) {
|
||||||
ETaxes.auth.checkAndProcess(callback);
|
ETaxes.auth.checkAndProcess(callback);
|
||||||
|
|
@ -236,7 +236,7 @@ function check_and_process_token_parties(callback) {
|
||||||
frappe.confirm(
|
frappe.confirm(
|
||||||
__('Your E-Taxes session has expired or authentication is required. Would you like to authenticate now?'),
|
__('Your E-Taxes session has expired or authentication is required. Would you like to authenticate now?'),
|
||||||
function() {
|
function() {
|
||||||
start_authentication_process_parties(callback);
|
start_authentication_process_customers(callback);
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
|
|
@ -258,8 +258,8 @@ function check_and_process_token_parties(callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для запуска полного процесса аутентификации
|
// Функция для запуска полного процесса аутентификации
|
||||||
function start_authentication_process_parties(callback) {
|
function start_authentication_process_customers(callback) {
|
||||||
show_loading_dialog_parties(
|
show_loading_dialog_customers(
|
||||||
__('Starting Authentication'),
|
__('Starting Authentication'),
|
||||||
__('Getting Asan Login settings...'),
|
__('Getting Asan Login settings...'),
|
||||||
__('Please wait')
|
__('Please wait')
|
||||||
|
|
@ -270,7 +270,7 @@ function start_authentication_process_parties(callback) {
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message && r.message.found) {
|
if (r.message && r.message.found) {
|
||||||
var asan_login_name = r.message.name;
|
var asan_login_name = r.message.name;
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Sending authentication request...'),
|
__('Sending authentication request...'),
|
||||||
__('This will send a request to your Asan Imza mobile app')
|
__('This will send a request to your Asan Imza mobile app')
|
||||||
);
|
);
|
||||||
|
|
@ -287,24 +287,24 @@ function start_authentication_process_parties(callback) {
|
||||||
|
|
||||||
// Отображаем код верификации, если он есть в ответе
|
// Отображаем код верификации, если он есть в ответе
|
||||||
if (r.message.verification_code) {
|
if (r.message.verification_code) {
|
||||||
$('#verification_code_parties').text(r.message.verification_code);
|
$('#verification_code_customers').text(r.message.verification_code);
|
||||||
$('#verification_code_container_parties').attr('style', 'display: block !important; margin-top: 15px;');
|
$('#verification_code_container_customers').attr('style', 'display: block !important; margin-top: 15px;');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Изменяем сообщение для ожидания подтверждения
|
// Изменяем сообщение для ожидания подтверждения
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Waiting for confirmation on your phone'),
|
__('Waiting for confirmation on your phone'),
|
||||||
__('Please check your phone and confirm the authentication request')
|
__('Please check your phone and confirm the authentication request')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Опрашиваем статус аутентификации
|
// Опрашиваем статус аутентификации
|
||||||
poll_authentication_status_parties(asan_login_name, bearer_token, function(success) {
|
poll_authentication_status_customers(asan_login_name, bearer_token, function(success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
// Если аутентификация успешна, продолжаем процесс
|
// Если аутентификация успешна, продолжаем процесс
|
||||||
complete_authentication_parties(asan_login_name, callback);
|
complete_authentication_customers(asan_login_name, callback);
|
||||||
} else {
|
} else {
|
||||||
// Если аутентификация не удалась, показываем сообщение об ошибке
|
// Если аутентификация не удалась, показываем сообщение об ошибке
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Authentication Failed'),
|
__('Authentication Failed'),
|
||||||
__('Could not authenticate with Asan Imza')
|
__('Could not authenticate with Asan Imza')
|
||||||
);
|
);
|
||||||
|
|
@ -312,7 +312,7 @@ function start_authentication_process_parties(callback) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Если запрос не удался, показываем сообщение об ошибке
|
// Если запрос не удался, показываем сообщение об ошибке
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Authentication Error'),
|
__('Authentication Error'),
|
||||||
r.message ? r.message.message : __('Authentication request failed')
|
r.message ? r.message.message : __('Authentication request failed')
|
||||||
);
|
);
|
||||||
|
|
@ -321,7 +321,7 @@ function start_authentication_process_parties(callback) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Если настройки не найдены, показываем сообщение об ошибке
|
// Если настройки не найдены, показываем сообщение об ошибке
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('No Asan Login Settings'),
|
__('No Asan Login Settings'),
|
||||||
__('Please configure Asan Login settings first')
|
__('Please configure Asan Login settings first')
|
||||||
);
|
);
|
||||||
|
|
@ -331,7 +331,7 @@ function start_authentication_process_parties(callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для опроса статуса аутентификации
|
// Функция для опроса статуса аутентификации
|
||||||
function poll_authentication_status_parties(asan_login_name, bearer_token, callback) {
|
function poll_authentication_status_customers(asan_login_name, bearer_token, callback) {
|
||||||
// Счетчик попыток и флаг для остановки опроса
|
// Счетчик попыток и флаг для остановки опроса
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 20; // 20 попыток с интервалом 6 секунд
|
const maxAttempts = 20; // 20 попыток с интервалом 6 секунд
|
||||||
|
|
@ -343,7 +343,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
||||||
if (attempts >= maxAttempts || stopPolling) {
|
if (attempts >= maxAttempts || stopPolling) {
|
||||||
if (attempts >= maxAttempts) {
|
if (attempts >= maxAttempts) {
|
||||||
// Показываем сообщение о таймауте
|
// Показываем сообщение о таймауте
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Authentication Timeout'),
|
__('Authentication Timeout'),
|
||||||
__('The authentication request has timed out. Please try again.')
|
__('The authentication request has timed out. Please try again.')
|
||||||
);
|
);
|
||||||
|
|
@ -355,7 +355,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
// Обновляем счетчик попыток в сообщении
|
// Обновляем счетчик попыток в сообщении
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Waiting for confirmation on your phone'),
|
__('Waiting for confirmation on your phone'),
|
||||||
__('Please check your phone and confirm the authentication request') +
|
__('Please check your phone and confirm the authentication request') +
|
||||||
' (' + attempts + '/' + maxAttempts + ')'
|
' (' + attempts + '/' + maxAttempts + ')'
|
||||||
|
|
@ -373,7 +373,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
||||||
if (r.message.authenticated) {
|
if (r.message.authenticated) {
|
||||||
// Авторизация успешна
|
// Авторизация успешна
|
||||||
stopPolling = true;
|
stopPolling = true;
|
||||||
set_loading_success_parties(
|
set_loading_success_customers(
|
||||||
__('Authentication Successful'),
|
__('Authentication Successful'),
|
||||||
__('You are now authenticated with Asan Imza'),
|
__('You are now authenticated with Asan Imza'),
|
||||||
function() {
|
function() {
|
||||||
|
|
@ -387,7 +387,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
||||||
} else {
|
} else {
|
||||||
// Если ошибка, останавливаем опрос и показываем сообщение
|
// Если ошибка, останавливаем опрос и показываем сообщение
|
||||||
stopPolling = true;
|
stopPolling = true;
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Authentication Error'),
|
__('Authentication Error'),
|
||||||
r.message ? r.message.message : __('An unknown error occurred')
|
r.message ? r.message.message : __('An unknown error occurred')
|
||||||
);
|
);
|
||||||
|
|
@ -407,8 +407,8 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для завершения аутентификации - получения сертификатов и выбора налогоплательщика
|
// Функция для завершения аутентификации - получения сертификатов и выбора налогоплательщика
|
||||||
function complete_authentication_parties(asan_login_name, callback) {
|
function complete_authentication_customers(asan_login_name, callback) {
|
||||||
show_loading_dialog_parties(
|
show_loading_dialog_customers(
|
||||||
__('Completing Authentication'),
|
__('Completing Authentication'),
|
||||||
__('Getting certificates...'),
|
__('Getting certificates...'),
|
||||||
__('Please wait')
|
__('Please wait')
|
||||||
|
|
@ -422,7 +422,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
||||||
},
|
},
|
||||||
callback: function(cert_r) {
|
callback: function(cert_r) {
|
||||||
if (cert_r.message && cert_r.message.success) {
|
if (cert_r.message && cert_r.message.success) {
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Certificates retrieved'),
|
__('Certificates retrieved'),
|
||||||
__('Selecting certificate and taxpayer')
|
__('Selecting certificate and taxpayer')
|
||||||
);
|
);
|
||||||
|
|
@ -440,7 +440,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
||||||
const cert_data = JSON.parse(r.message.selected_certificate_json);
|
const cert_data = JSON.parse(r.message.selected_certificate_json);
|
||||||
const cert_name = r.message.selected_certificate;
|
const cert_name = r.message.selected_certificate;
|
||||||
|
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Selecting certificate'),
|
__('Selecting certificate'),
|
||||||
cert_name
|
cert_name
|
||||||
);
|
);
|
||||||
|
|
@ -455,7 +455,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
||||||
},
|
},
|
||||||
callback: function(select_r) {
|
callback: function(select_r) {
|
||||||
if (select_r.message && select_r.message.success) {
|
if (select_r.message && select_r.message.success) {
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Selecting taxpayer'),
|
__('Selecting taxpayer'),
|
||||||
__('Using certificate: ') + cert_name
|
__('Using certificate: ') + cert_name
|
||||||
);
|
);
|
||||||
|
|
@ -468,37 +468,37 @@ function complete_authentication_parties(asan_login_name, callback) {
|
||||||
},
|
},
|
||||||
callback: function(tax_r) {
|
callback: function(tax_r) {
|
||||||
if (tax_r.message && tax_r.message.success) {
|
if (tax_r.message && tax_r.message.success) {
|
||||||
set_loading_success_parties(
|
set_loading_success_customers(
|
||||||
__('Authentication Complete'),
|
__('Authentication Complete'),
|
||||||
__('You can now access E-Taxes services'),
|
__('You can now access E-Taxes services'),
|
||||||
callback,
|
callback,
|
||||||
2000 // Автоматически закроется через 2 секунды
|
2000 // Автоматически закроется через 2 секунды
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing certificate:', e);
|
console.error('Error parsing certificate:', e);
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hide_loading_dialog_parties();
|
hide_loading_dialog_customers();
|
||||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Error'),
|
__('Error'),
|
||||||
cert_r.message ? cert_r.message.message : __('Failed to get certificates')
|
cert_r.message ? cert_r.message.message : __('Failed to get certificates')
|
||||||
);
|
);
|
||||||
|
|
@ -508,7 +508,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для отображения селектора сертификатов
|
// Функция для отображения селектора сертификатов
|
||||||
function show_certificate_selector_parties(certificates, asan_login_name, callback) {
|
function show_certificate_selector_customers(certificates, asan_login_name, callback) {
|
||||||
if (!certificates || !certificates.length) {
|
if (!certificates || !certificates.length) {
|
||||||
frappe.msgprint(__('No certificates available'));
|
frappe.msgprint(__('No certificates available'));
|
||||||
return;
|
return;
|
||||||
|
|
@ -536,7 +536,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
'<td>' + id + '</td>' +
|
'<td>' + id + '</td>' +
|
||||||
'<td>' + (cert.position || '') + '</td>' +
|
'<td>' + (cert.position || '') + '</td>' +
|
||||||
'<td>' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '</td>' +
|
'<td>' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '</td>' +
|
||||||
'<td><button class="btn btn-xs btn-primary select-cert-parties" data-index="' + index + '">Select</button></td>' +
|
'<td><button class="btn btn-xs btn-primary select-cert-customers" data-index="' + index + '">Select</button></td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -554,7 +554,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
// Обработчик нажатия на кнопку выбора сертификата
|
// Обработчик нажатия на кнопку выбора сертификата
|
||||||
dialog.$wrapper.find('.select-cert-parties').on('click', function() {
|
dialog.$wrapper.find('.select-cert-customers').on('click', function() {
|
||||||
var index = $(this).data('index');
|
var index = $(this).data('index');
|
||||||
var cert = certificates[index];
|
var cert = certificates[index];
|
||||||
|
|
||||||
|
|
@ -571,7 +571,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
|
|
||||||
// Показываем прогресс
|
// Показываем прогресс
|
||||||
show_loading_dialog_parties(
|
show_loading_dialog_customers(
|
||||||
__('Selecting Certificate'),
|
__('Selecting Certificate'),
|
||||||
__('Processing your selection...'),
|
__('Processing your selection...'),
|
||||||
certName
|
certName
|
||||||
|
|
@ -587,7 +587,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message && r.message.success) {
|
if (r.message && r.message.success) {
|
||||||
update_loading_message_parties(
|
update_loading_message_customers(
|
||||||
__('Certificate selected'),
|
__('Certificate selected'),
|
||||||
__('Now selecting taxpayer information')
|
__('Now selecting taxpayer information')
|
||||||
);
|
);
|
||||||
|
|
@ -600,14 +600,14 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message && r.message.success) {
|
if (r.message && r.message.success) {
|
||||||
set_loading_success_parties(
|
set_loading_success_customers(
|
||||||
__('Authentication Complete'),
|
__('Authentication Complete'),
|
||||||
__('You can now access E-Taxes services'),
|
__('You can now access E-Taxes services'),
|
||||||
callback,
|
callback,
|
||||||
2000 // Автоматически закроется через 2 секунды
|
2000 // Автоматически закроется через 2 секунды
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Error'),
|
__('Error'),
|
||||||
r.message ? r.message.message : __('Failed to select taxpayer')
|
r.message ? r.message.message : __('Failed to select taxpayer')
|
||||||
);
|
);
|
||||||
|
|
@ -615,7 +615,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
set_loading_error_parties(
|
set_loading_error_customers(
|
||||||
__('Error'),
|
__('Error'),
|
||||||
r.message ? r.message.message : __('Failed to select certificate')
|
r.message ? r.message.message : __('Failed to select certificate')
|
||||||
);
|
);
|
||||||
|
|
@ -625,14 +625,14 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_invoice_filter_dialog() {
|
function show_invoice_filter_dialog_customers() {
|
||||||
const minDate = moment("2020-01-01", "YYYY-MM-DD");
|
const minDate = moment("2020-01-01", "YYYY-MM-DD");
|
||||||
const prevYear = moment().subtract(1, 'year').year();
|
const prevYear = moment().subtract(1, 'year').year();
|
||||||
const startDate = prevYear + "-01-01";
|
const startDate = prevYear + "-01-01";
|
||||||
const endDate = moment().format('YYYY-MM-DD');
|
const endDate = moment().format('YYYY-MM-DD');
|
||||||
|
|
||||||
var d = new frappe.ui.Dialog({
|
var d = new frappe.ui.Dialog({
|
||||||
title: __('Party Filters'),
|
title: __('Customer Filters'),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: 'date_range_section',
|
fieldname: 'date_range_section',
|
||||||
|
|
@ -652,7 +652,7 @@ function show_invoice_filter_dialog() {
|
||||||
default: endDate
|
default: endDate
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
primary_action_label: __('Load Parties'),
|
primary_action_label: __('Load Customers'),
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
var values = d.get_values();
|
var values = d.get_values();
|
||||||
|
|
||||||
|
|
@ -676,29 +676,29 @@ function show_invoice_filter_dialog() {
|
||||||
let toDate = values.creationDateTo ? moment(values.creationDateTo).format('DD-MM-YYYY 23:59') : moment(endDate).format('DD-MM-YYYY 23:59');
|
let toDate = values.creationDateTo ? moment(values.creationDateTo).format('DD-MM-YYYY 23:59') : moment(endDate).format('DD-MM-YYYY 23:59');
|
||||||
|
|
||||||
// Запускаем загрузку с прогресс-баром
|
// Запускаем загрузку с прогресс-баром
|
||||||
load_parties_with_pagination(fromDate, toDate, 0, []);
|
load_customers_with_pagination(fromDate, toDate, 0, []);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
d.show();
|
d.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕННАЯ функция загрузки контрагентов с пагинацией
|
// ИСПРАВЛЕННАЯ функция загрузки customers с пагинацией
|
||||||
function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_data = null) {
|
function load_customers_with_pagination(fromDate, toDate, offset = 0, accumulated_data = null) {
|
||||||
const maxCount = 200;
|
const maxCount = 200;
|
||||||
|
|
||||||
// Инициализируем accumulated_data если это первый вызов
|
// Инициализируем accumulated_data если это первый вызов
|
||||||
if (!accumulated_data) {
|
if (!accumulated_data) {
|
||||||
accumulated_data = {
|
accumulated_data = {
|
||||||
created_count: 0,
|
customers_created: 0,
|
||||||
|
suppliers_created: 0,
|
||||||
skipped_count: 0,
|
skipped_count: 0,
|
||||||
failed_count: 0,
|
failed_count: 0,
|
||||||
total_invoices: 0,
|
total_invoices: 0
|
||||||
unique_parties: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Fetching parties from E-Taxes...'),
|
message: __('Fetching customers from E-Taxes...'),
|
||||||
indicator: 'blue'
|
indicator: 'blue'
|
||||||
}, 3);
|
}, 3);
|
||||||
}
|
}
|
||||||
|
|
@ -727,18 +727,18 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
||||||
if (hasMore && currentInvoices.length > 0) {
|
if (hasMore && currentInvoices.length > 0) {
|
||||||
// Если есть еще инвойсы, загружаем следующую партию
|
// Если есть еще инвойсы, загружаем следующую партию
|
||||||
let newOffset = offset + currentInvoices.length;
|
let newOffset = offset + currentInvoices.length;
|
||||||
load_parties_with_pagination(fromDate, toDate, newOffset, accumulated_data);
|
load_customers_with_pagination(fromDate, toDate, newOffset, accumulated_data);
|
||||||
} else {
|
} else {
|
||||||
// Если больше нет инвойсов, переходим к обработке
|
// Если больше нет инвойсов, переходим к обработке
|
||||||
if (allInvoices.length > 0) {
|
if (allInvoices.length > 0) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Found {0} invoices. Processing parties...', [allInvoices.length]),
|
message: __('Found {0} invoices. Processing customers...', [allInvoices.length]),
|
||||||
indicator: 'green'
|
indicator: 'green'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
// Теперь обрабатываем инвойсы батчами с прогресс баром
|
// Теперь обрабатываем инвойсы батчами с прогресс баром
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
process_invoices_for_parties_with_progress(allInvoices, token);
|
process_invoices_for_customers_with_progress(allInvoices, token);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
|
|
@ -752,8 +752,8 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
||||||
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() {
|
||||||
check_and_process_token_parties(function() {
|
check_and_process_token_customers(function() {
|
||||||
show_invoice_filter_dialog();
|
show_invoice_filter_dialog_customers();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
|
|
@ -767,13 +767,13 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Error'),
|
title: __('Error'),
|
||||||
indicator: 'red',
|
indicator: 'red',
|
||||||
message: r.message ? r.message.message : __('An error occurred while loading parties')
|
message: r.message ? r.message.message : __('An error occurred while loading customers')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
// ИСПРАВЛЕНО: Очищаем состояние при ошибке
|
// ИСПРАВЛЕНО: Очищаем состояние при ошибке
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Network Error'),
|
title: __('Network Error'),
|
||||||
indicator: 'red',
|
indicator: 'red',
|
||||||
|
|
@ -783,33 +783,33 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕННАЯ функция обработки инвойсов для контрагентов с прогресс баром
|
// ИСПРАВЛЕННАЯ функция обработки инвойсов для customers с прогресс баром
|
||||||
function process_invoices_for_parties_with_progress(invoices, token, processedCount = 0, currentIndex = 0, batchSize = 50) {
|
function process_invoices_for_customers_with_progress(invoices, token, processedCount = 0, currentIndex = 0, batchSize = 50) {
|
||||||
// Инициализация при первом вызове
|
// Инициализация при первом вызове
|
||||||
if (processedCount === 0 && currentIndex === 0) {
|
if (processedCount === 0 && currentIndex === 0) {
|
||||||
window.cancelPartiesLoading = false;
|
window.cancelCustomersLoading = false;
|
||||||
window.totalPartiesToProcess = invoices.length;
|
window.totalCustomersToProcess = invoices.length;
|
||||||
|
|
||||||
// Показываем прогресс бар
|
// Показываем прогресс бар
|
||||||
frappe.show_progress(__('Processing Parties'), 0, invoices.length,
|
frappe.show_progress(__('Processing Customers'), 0, invoices.length,
|
||||||
__('Extracting parties from invoices...'), null, true);
|
__('Extracting customers from invoices...'), null, true);
|
||||||
|
|
||||||
// Добавляем обработчик отмены
|
// Добавляем обработчик отмены
|
||||||
$(document).off('progress-cancel.loading_parties');
|
$(document).off('progress-cancel.loading_customers');
|
||||||
$(document).on('progress-cancel.loading_parties', function() {
|
$(document).on('progress-cancel.loading_customers', function() {
|
||||||
window.cancelPartiesLoading = true;
|
window.cancelCustomersLoading = true;
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Cancelling party extraction...'),
|
message: __('Cancelling customer extraction...'),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
}, 3);
|
}, 3);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка отмены
|
// Проверка отмены
|
||||||
if (window.cancelPartiesLoading) {
|
if (window.cancelCustomersLoading) {
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Party extraction cancelled'),
|
message: __('Customer extraction cancelled'),
|
||||||
indicator: 'red'
|
indicator: 'red'
|
||||||
}, 3);
|
}, 3);
|
||||||
return;
|
return;
|
||||||
|
|
@ -817,14 +817,14 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
||||||
|
|
||||||
// Если все инвойсы обработаны
|
// Если все инвойсы обработаны
|
||||||
if (currentIndex >= invoices.length) {
|
if (currentIndex >= invoices.length) {
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
|
|
||||||
// Показываем итоговый результат
|
// Показываем итоговый результат
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Parties Loaded Successfully'),
|
title: __('Customers Loaded Successfully'),
|
||||||
indicator: 'green',
|
indicator: 'green',
|
||||||
message: __('Created {0} unique parties. Skipped {1} duplicates. Total invoices processed: {2}',
|
message: __('Created {0} customers and {1} suppliers. Total invoices processed: {2}',
|
||||||
[processedCount, 0, invoices.length]) // skipped считается на сервере
|
[processedCount, 0, invoices.length]) // suppliers считается на сервере
|
||||||
});
|
});
|
||||||
|
|
||||||
// ИСПРАВЛЕНО: Обновляем список с задержкой для уверенности в очистке состояния
|
// ИСПРАВЛЕНО: Обновляем список с задержкой для уверенности в очистке состояния
|
||||||
|
|
@ -842,7 +842,7 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
||||||
const currentBatch = invoices.slice(currentIndex, currentIndex + currentBatchSize);
|
const currentBatch = invoices.slice(currentIndex, currentIndex + currentBatchSize);
|
||||||
|
|
||||||
// Обновляем прогресс
|
// Обновляем прогресс
|
||||||
frappe.show_progress(__('Processing Parties'), currentIndex, invoices.length,
|
frappe.show_progress(__('Processing Customers'), currentIndex, invoices.length,
|
||||||
__('Processing batch {0}-{1} of {2} invoices', [currentIndex + 1, currentIndex + currentBatchSize, invoices.length]));
|
__('Processing batch {0}-{1} of {2} invoices', [currentIndex + 1, currentIndex + currentBatchSize, invoices.length]));
|
||||||
|
|
||||||
// Обрабатываем текущий батч
|
// Обрабатываем текущий батч
|
||||||
|
|
@ -855,29 +855,29 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message && r.message.success) {
|
if (r.message && r.message.success) {
|
||||||
// Обновляем накопленные данные
|
// Обновляем накопленные данные
|
||||||
processedCount += r.message.created_count || 0;
|
processedCount += (r.message.customers_created || 0) + (r.message.suppliers_created || 0);
|
||||||
|
|
||||||
// Переходим к следующему батчу с небольшой задержкой
|
// Переходим к следующему батчу с небольшой задержкой
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||||
currentIndex + currentBatchSize, batchSize);
|
currentIndex + currentBatchSize, batchSize);
|
||||||
}
|
}
|
||||||
}, 100); // 100ms задержка между батчами
|
}, 100); // 100ms задержка между батчами
|
||||||
|
|
||||||
} else if (r.message && r.message.error === 'unauthorized') {
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
cleanup_parties_loading_state();
|
cleanup_customers_loading_state();
|
||||||
|
|
||||||
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() {
|
||||||
check_and_process_token_parties(function() {
|
check_and_process_token_customers(function() {
|
||||||
// Получаем новый токен и продолжаем с текущего индекса
|
// Получаем новый токен и продолжаем с текущего индекса
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'invoice_az.auth.get_default_asan_login',
|
method: 'invoice_az.auth.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) {
|
||||||
process_invoices_for_parties_with_progress(invoices, login_r.message.main_token,
|
process_invoices_for_customers_with_progress(invoices, login_r.message.main_token,
|
||||||
processedCount, currentIndex, batchSize);
|
processedCount, currentIndex, batchSize);
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint(__('Failed to get authentication token'));
|
frappe.msgprint(__('Failed to get authentication token'));
|
||||||
|
|
@ -896,8 +896,8 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
||||||
} else {
|
} else {
|
||||||
// Ошибка обработки батча - продолжаем со следующим
|
// Ошибка обработки батча - продолжаем со следующим
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||||
currentIndex + currentBatchSize, batchSize);
|
currentIndex + currentBatchSize, batchSize);
|
||||||
}
|
}
|
||||||
}, 200); // Увеличенная задержка при ошибках
|
}, 200); // Увеличенная задержка при ошибках
|
||||||
|
|
@ -906,8 +906,8 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
// Сетевая ошибка - продолжаем со следующим батчом
|
// Сетевая ошибка - продолжаем со следующим батчом
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||||
currentIndex + currentBatchSize, batchSize);
|
currentIndex + currentBatchSize, batchSize);
|
||||||
}
|
}
|
||||||
}, 500); // Еще большая задержка при сетевых ошибках
|
}, 500); // Еще большая задержка при сетевых ошибках
|
||||||
|
|
@ -0,0 +1,916 @@
|
||||||
|
// Настройки списка E-Taxes Suppliers с прогресс баром
|
||||||
|
frappe.listview_settings['E-Taxes Suppliers'] = {
|
||||||
|
add_fields: ['status', 'mapped_supplier'],
|
||||||
|
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
if (doc.status === 'Mapped') {
|
||||||
|
return [__('Mapped'), 'green', 'status,=,Mapped'];
|
||||||
|
} else if (doc.status === 'Processing') {
|
||||||
|
return [__('Processing'), 'orange', 'status,=,Processing'];
|
||||||
|
} else {
|
||||||
|
return [__('New'), 'red', 'status,=,New'];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onload: function(listview) {
|
||||||
|
// Add button to load suppliers from E-Taxes
|
||||||
|
listview.page.add_menu_item(__('Load from E-Taxes'), function() {
|
||||||
|
// Сначала проверяем актуальность токена перед загрузкой
|
||||||
|
check_and_process_token_suppliers(function() {
|
||||||
|
show_invoice_filter_dialog_suppliers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Глобальная переменная для хранения диалога загрузки
|
||||||
|
var loading_dialog_suppliers = null;
|
||||||
|
|
||||||
|
// Очистка состояния при закрытии/перезагрузке страницы
|
||||||
|
$(window).on('beforeunload', function() {
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Очистка состояния при переходе на другую страницу
|
||||||
|
$(document).on('page:before-change', function() {
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
});
|
||||||
|
|
||||||
|
// НОВАЯ ФУНКЦИЯ: Очистка состояния загрузки suppliers
|
||||||
|
function cleanup_suppliers_loading_state() {
|
||||||
|
try {
|
||||||
|
// Скрываем прогресс бар
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
// Удаляем event listeners
|
||||||
|
$(document).off('progress-cancel.loading_suppliers');
|
||||||
|
|
||||||
|
// Очищаем глобальные переменные
|
||||||
|
if (typeof window.cancelSuppliersLoading !== 'undefined') {
|
||||||
|
delete window.cancelSuppliersLoading;
|
||||||
|
}
|
||||||
|
if (typeof window.totalSuppliersToProcess !== 'undefined') {
|
||||||
|
delete window.totalSuppliersToProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО: Скрываем все возможные диалоги загрузки
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
|
||||||
|
// Дополнительная очистка - удаляем все модальные окна, которые могут висеть
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open');
|
||||||
|
|
||||||
|
// Очищаем любые блокировки интерфейса
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
|
||||||
|
console.log('Suppliers loading state cleaned up');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error during suppliers cleanup:', e);
|
||||||
|
// В крайнем случае, принудительно разблокируем интерфейс
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для отображения диалога загрузки с анимацией
|
||||||
|
function show_loading_dialog_suppliers(title, message, submessage) {
|
||||||
|
// Если диалог уже открыт, обновляем только сообщение
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
$('#loading_title_suppliers').text(title);
|
||||||
|
$('#loading_message_suppliers').text(message);
|
||||||
|
if (submessage) {
|
||||||
|
$('#loading_submessage_suppliers').text(submessage).show();
|
||||||
|
} else {
|
||||||
|
$('#loading_submessage_suppliers').hide();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем диалог с анимацией загрузки
|
||||||
|
loading_dialog_suppliers = new frappe.ui.Dialog({
|
||||||
|
title: title,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'loading_html',
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
options: `
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="lds-dual-ring" 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>
|
||||||
|
@keyframes lds-dual-ring {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="mt-3">
|
||||||
|
<h4 id="loading_title_suppliers">${title}</h4>
|
||||||
|
<p id="loading_message_suppliers">${message}</p>
|
||||||
|
<p id="loading_submessage_suppliers" style="${submessage ? '' : 'display:none'}">${submessage || ''}</p>
|
||||||
|
<!-- Блок для отображения кода верификации -->
|
||||||
|
<div id="verification_code_container_suppliers" style="display:none; margin-top: 15px;">
|
||||||
|
<div style="background: #f8f9fa; padding: 10px; display: inline-block; border-radius: 5px; font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #333; border: 1px solid #ddd;">
|
||||||
|
<span id="verification_code_suppliers">----</span>
|
||||||
|
</div>
|
||||||
|
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
loading_dialog_suppliers.show();
|
||||||
|
loading_dialog_suppliers.$wrapper.find('.modal-dialog').css('max-width', '450px');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ИСПРАВЛЕННАЯ функция для скрытия диалога загрузки
|
||||||
|
function hide_loading_dialog_suppliers() {
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
try {
|
||||||
|
loading_dialog_suppliers.hide();
|
||||||
|
loading_dialog_suppliers = null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error hiding loading dialog suppliers:', e);
|
||||||
|
loading_dialog_suppliers = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для обновления сообщения в диалоге загрузки
|
||||||
|
function update_loading_message_suppliers(message, submessage) {
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
$('#loading_message_suppliers').text(message);
|
||||||
|
if (submessage !== undefined) {
|
||||||
|
$('#loading_submessage_suppliers').text(submessage);
|
||||||
|
$('#loading_submessage_suppliers').toggle(!!submessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для изменения статуса загрузки на успешный с автоматическим закрытием
|
||||||
|
function set_loading_success_suppliers(message, submessage, callback, delay = 2000) {
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
// Меняем анимацию на галочку
|
||||||
|
loading_dialog_suppliers.$wrapper.find('.lds-dual-ring').html(`
|
||||||
|
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Обновляем сообщения
|
||||||
|
$('#loading_message_suppliers').text(message);
|
||||||
|
if (submessage !== undefined) {
|
||||||
|
$('#loading_submessage_suppliers').text(submessage);
|
||||||
|
$('#loading_submessage_suppliers').toggle(!!submessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрываем код верификации при успешном завершении
|
||||||
|
$('#verification_code_container_suppliers').hide();
|
||||||
|
|
||||||
|
// Удаляем все существующие кнопки действий
|
||||||
|
loading_dialog_suppliers.set_primary_action(null);
|
||||||
|
loading_dialog_suppliers.set_secondary_action(null);
|
||||||
|
|
||||||
|
// Автоматически закрываем через указанную задержку
|
||||||
|
setTimeout(function() {
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
loading_dialog_suppliers.hide();
|
||||||
|
loading_dialog_suppliers = null;
|
||||||
|
}
|
||||||
|
if (callback) callback();
|
||||||
|
}, delay);
|
||||||
|
} else if (callback) {
|
||||||
|
// Если диалог не открыт, но есть callback, вызываем его
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для изменения статуса загрузки на ошибку
|
||||||
|
function set_loading_error_suppliers(message, submessage, show_close_button = true) {
|
||||||
|
if (loading_dialog_suppliers) {
|
||||||
|
// Меняем анимацию на крестик
|
||||||
|
loading_dialog_suppliers.$wrapper.find('.lds-dual-ring').html(`
|
||||||
|
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<i class="fa fa-times" style="font-size: 48px; color: #d9534f;"></i>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Обновляем сообщения
|
||||||
|
$('#loading_message_suppliers').text(message);
|
||||||
|
if (submessage !== undefined) {
|
||||||
|
$('#loading_submessage_suppliers').text(submessage);
|
||||||
|
$('#loading_submessage_suppliers').toggle(!!submessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрываем код верификации при ошибке
|
||||||
|
$('#verification_code_container_suppliers').hide();
|
||||||
|
|
||||||
|
// При необходимости добавляем кнопку закрытия
|
||||||
|
if (show_close_button) {
|
||||||
|
loading_dialog_suppliers.set_primary_action(__('Close'), function() {
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для проверки токена и обработки аутентификации при необходимости
|
||||||
|
function check_and_process_token_suppliers(callback) {
|
||||||
|
// Проверяем, доступен ли новый модуль ETaxes
|
||||||
|
if (typeof ETaxes !== 'undefined' && ETaxes.auth) {
|
||||||
|
ETaxes.auth.checkAndProcess(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback на старый код
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.check_token_validity',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.valid) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Your E-Taxes session has expired or authentication is required. Would you like to authenticate now?'),
|
||||||
|
function() {
|
||||||
|
start_authentication_process_suppliers(callback);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Authentication canceled. Operation cannot be completed.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to check authentication status')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для запуска полного процесса аутентификации
|
||||||
|
function start_authentication_process_suppliers(callback) {
|
||||||
|
show_loading_dialog_suppliers(
|
||||||
|
__('Starting Authentication'),
|
||||||
|
__('Getting Asan Login settings...'),
|
||||||
|
__('Please wait')
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.get_default_asan_login',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.found) {
|
||||||
|
var asan_login_name = r.message.name;
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Sending authentication request...'),
|
||||||
|
__('This will send a request to your Asan Imza mobile app')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Запускаем процесс аутентификации
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.handle_authentication',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
const bearer_token = r.message.bearer_token;
|
||||||
|
|
||||||
|
// Отображаем код верификации, если он есть в ответе
|
||||||
|
if (r.message.verification_code) {
|
||||||
|
$('#verification_code_suppliers').text(r.message.verification_code);
|
||||||
|
$('#verification_code_container_suppliers').attr('style', 'display: block !important; margin-top: 15px;');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Изменяем сообщение для ожидания подтверждения
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Waiting for confirmation on your phone'),
|
||||||
|
__('Please check your phone and confirm the authentication request')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Опрашиваем статус аутентификации
|
||||||
|
poll_authentication_status_suppliers(asan_login_name, bearer_token, function(success) {
|
||||||
|
if (success) {
|
||||||
|
// Если аутентификация успешна, продолжаем процесс
|
||||||
|
complete_authentication_suppliers(asan_login_name, callback);
|
||||||
|
} else {
|
||||||
|
// Если аутентификация не удалась, показываем сообщение об ошибке
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Authentication Failed'),
|
||||||
|
__('Could not authenticate with Asan Imza')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Если запрос не удался, показываем сообщение об ошибке
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Authentication Error'),
|
||||||
|
r.message ? r.message.message : __('Authentication request failed')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Если настройки не найдены, показываем сообщение об ошибке
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('No Asan Login Settings'),
|
||||||
|
__('Please configure Asan Login settings first')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для опроса статуса аутентификации
|
||||||
|
function poll_authentication_status_suppliers(asan_login_name, bearer_token, callback) {
|
||||||
|
// Счетчик попыток и флаг для остановки опроса
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 20; // 20 попыток с интервалом 6 секунд
|
||||||
|
let stopPolling = false;
|
||||||
|
|
||||||
|
// Функция опроса
|
||||||
|
function pollStatus() {
|
||||||
|
// Если достигнуто максимальное количество попыток или установлен флаг остановки
|
||||||
|
if (attempts >= maxAttempts || stopPolling) {
|
||||||
|
if (attempts >= maxAttempts) {
|
||||||
|
// Показываем сообщение о таймауте
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Authentication Timeout'),
|
||||||
|
__('The authentication request has timed out. Please try again.')
|
||||||
|
);
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
// Обновляем счетчик попыток в сообщении
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Waiting for confirmation on your phone'),
|
||||||
|
__('Please check your phone and confirm the authentication request') +
|
||||||
|
' (' + attempts + '/' + maxAttempts + ')'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Проверяем статус авторизации
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.poll_auth_status',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name,
|
||||||
|
'bearer_token': bearer_token
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
if (r.message.authenticated) {
|
||||||
|
// Авторизация успешна
|
||||||
|
stopPolling = true;
|
||||||
|
set_loading_success_suppliers(
|
||||||
|
__('Authentication Successful'),
|
||||||
|
__('You are now authenticated with Asan Imza'),
|
||||||
|
function() {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Продолжаем опрос
|
||||||
|
setTimeout(pollStatus, 6000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Если ошибка, останавливаем опрос и показываем сообщение
|
||||||
|
stopPolling = true;
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Authentication Error'),
|
||||||
|
r.message ? r.message.message : __('An unknown error occurred')
|
||||||
|
);
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
// В случае ошибки продолжаем опрос
|
||||||
|
console.error('Error during status check:', err);
|
||||||
|
setTimeout(pollStatus, 6000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Начинаем опрос
|
||||||
|
pollStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для завершения аутентификации - получения сертификатов и выбора налогоплательщика
|
||||||
|
function complete_authentication_suppliers(asan_login_name, callback) {
|
||||||
|
show_loading_dialog_suppliers(
|
||||||
|
__('Completing Authentication'),
|
||||||
|
__('Getting certificates...'),
|
||||||
|
__('Please wait')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Получаем сертификаты
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.get_auth_certificates',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name
|
||||||
|
},
|
||||||
|
callback: function(cert_r) {
|
||||||
|
if (cert_r.message && cert_r.message.success) {
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Certificates retrieved'),
|
||||||
|
__('Selecting certificate and taxpayer')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Получаем текущий документ для проверки выбранного сертификата
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get',
|
||||||
|
args: {
|
||||||
|
doctype: 'Asan Login',
|
||||||
|
name: asan_login_name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.selected_certificate_json) {
|
||||||
|
try {
|
||||||
|
const cert_data = JSON.parse(r.message.selected_certificate_json);
|
||||||
|
const cert_name = r.message.selected_certificate;
|
||||||
|
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Selecting certificate'),
|
||||||
|
cert_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// Выбираем сертификат
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.select_certificate',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name,
|
||||||
|
'certificate_data': cert_data,
|
||||||
|
'certificate_name': cert_name
|
||||||
|
},
|
||||||
|
callback: function(select_r) {
|
||||||
|
if (select_r.message && select_r.message.success) {
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Selecting taxpayer'),
|
||||||
|
__('Using certificate: ') + cert_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// Выбираем налогоплательщика
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.select_taxpayer',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name
|
||||||
|
},
|
||||||
|
callback: function(tax_r) {
|
||||||
|
if (tax_r.message && tax_r.message.success) {
|
||||||
|
set_loading_success_suppliers(
|
||||||
|
__('Authentication Complete'),
|
||||||
|
__('You can now access E-Taxes services'),
|
||||||
|
callback,
|
||||||
|
2000 // Автоматически закроется через 2 секунды
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
show_certificate_selector_suppliers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
show_certificate_selector_suppliers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing certificate:', e);
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
show_certificate_selector_suppliers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hide_loading_dialog_suppliers();
|
||||||
|
show_certificate_selector_suppliers(cert_r.message.certificates, asan_login_name, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Error'),
|
||||||
|
cert_r.message ? cert_r.message.message : __('Failed to get certificates')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для отображения селектора сертификатов
|
||||||
|
function show_certificate_selector_suppliers(certificates, asan_login_name, callback) {
|
||||||
|
if (!certificates || !certificates.length) {
|
||||||
|
frappe.msgprint(__('No certificates available'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем таблицу сертификатов
|
||||||
|
var cert_html = '<div style="max-height: 400px; overflow-y: auto;"><table class="table table-bordered">';
|
||||||
|
cert_html += '<thead><tr><th>Type</th><th>Name</th><th>ID</th><th>Position</th><th>Status</th><th></th></tr></thead><tbody>';
|
||||||
|
|
||||||
|
certificates.forEach(function(cert, index) {
|
||||||
|
var name = '';
|
||||||
|
var id = '';
|
||||||
|
|
||||||
|
if (cert.taxpayerType === 'individual' && cert.individualInfo) {
|
||||||
|
name = cert.individualInfo.name || '';
|
||||||
|
id = cert.individualInfo.fin || '';
|
||||||
|
} else if (cert.legalInfo) {
|
||||||
|
name = cert.legalInfo.name || '';
|
||||||
|
id = cert.legalInfo.tin || cert.legalInfo.voen || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cert_html += '<tr>' +
|
||||||
|
'<td>' + (cert.taxpayerType || '') + '</td>' +
|
||||||
|
'<td>' + name + '</td>' +
|
||||||
|
'<td>' + id + '</td>' +
|
||||||
|
'<td>' + (cert.position || '') + '</td>' +
|
||||||
|
'<td>' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '</td>' +
|
||||||
|
'<td><button class="btn btn-xs btn-primary select-cert-suppliers" data-index="' + index + '">Select</button></td>' +
|
||||||
|
'</tr>';
|
||||||
|
});
|
||||||
|
|
||||||
|
cert_html += '</tbody></table></div>';
|
||||||
|
|
||||||
|
var dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Select Certificate'),
|
||||||
|
fields: [{
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
fieldname: 'certificates',
|
||||||
|
options: cert_html
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
// Обработчик нажатия на кнопку выбора сертификата
|
||||||
|
dialog.$wrapper.find('.select-cert-suppliers').on('click', function() {
|
||||||
|
var index = $(this).data('index');
|
||||||
|
var cert = certificates[index];
|
||||||
|
|
||||||
|
// Формируем имя для отображения
|
||||||
|
let certName = '';
|
||||||
|
if (cert.taxpayerType === 'individual' && cert.individualInfo) {
|
||||||
|
certName = `${cert.individualInfo.name} (${cert.individualInfo.fin})`;
|
||||||
|
} else if (cert.legalInfo) {
|
||||||
|
certName = `${cert.legalInfo.name} (${cert.legalInfo.voen || cert.legalInfo.tin})`;
|
||||||
|
} else {
|
||||||
|
certName = `Certificate ${index + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.hide();
|
||||||
|
|
||||||
|
// Показываем прогресс
|
||||||
|
show_loading_dialog_suppliers(
|
||||||
|
__('Selecting Certificate'),
|
||||||
|
__('Processing your selection...'),
|
||||||
|
certName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Отправляем запрос на выбор сертификата
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.select_certificate',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name,
|
||||||
|
'certificate_data': cert,
|
||||||
|
'certificate_name': certName
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
update_loading_message_suppliers(
|
||||||
|
__('Certificate selected'),
|
||||||
|
__('Now selecting taxpayer information')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Выбираем налогоплательщика
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.select_taxpayer',
|
||||||
|
args: {
|
||||||
|
'asan_login_name': asan_login_name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
set_loading_success_suppliers(
|
||||||
|
__('Authentication Complete'),
|
||||||
|
__('You can now access E-Taxes services'),
|
||||||
|
callback,
|
||||||
|
2000 // Автоматически закроется через 2 секунды
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Error'),
|
||||||
|
r.message ? r.message.message : __('Failed to select taxpayer')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
set_loading_error_suppliers(
|
||||||
|
__('Error'),
|
||||||
|
r.message ? r.message.message : __('Failed to select certificate')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_invoice_filter_dialog_suppliers() {
|
||||||
|
const minDate = moment("2020-01-01", "YYYY-MM-DD");
|
||||||
|
const prevYear = moment().subtract(1, 'year').year();
|
||||||
|
const startDate = prevYear + "-01-01";
|
||||||
|
const endDate = moment().format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
var d = new frappe.ui.Dialog({
|
||||||
|
title: __('Supplier Filters'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'date_range_section',
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
label: __('Date Range')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'creationDateFrom',
|
||||||
|
fieldtype: 'Date',
|
||||||
|
label: __('From Date'),
|
||||||
|
default: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'creationDateTo',
|
||||||
|
fieldtype: 'Date',
|
||||||
|
label: __('To Date'),
|
||||||
|
default: endDate
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action_label: __('Load Suppliers'),
|
||||||
|
primary_action: function() {
|
||||||
|
var values = d.get_values();
|
||||||
|
|
||||||
|
const fromDateMoment = moment(values.creationDateFrom, "YYYY-MM-DD");
|
||||||
|
const toDateMoment = moment(values.creationDateTo, "YYYY-MM-DD");
|
||||||
|
const todayMoment = moment();
|
||||||
|
|
||||||
|
if (fromDateMoment.isBefore(minDate)) {
|
||||||
|
frappe.msgprint(__('From Date cannot be earlier than January 1, 2020'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDateMoment.isAfter(todayMoment)) {
|
||||||
|
frappe.msgprint(__('To Date cannot be later than today'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.hide();
|
||||||
|
|
||||||
|
let fromDate = values.creationDateFrom ? moment(values.creationDateFrom).format('DD-MM-YYYY 00:00') : moment(startDate).format('DD-MM-YYYY 00:00');
|
||||||
|
let toDate = values.creationDateTo ? moment(values.creationDateTo).format('DD-MM-YYYY 23:59') : moment(endDate).format('DD-MM-YYYY 23:59');
|
||||||
|
|
||||||
|
// Запускаем загрузку с прогресс-баром
|
||||||
|
load_suppliers_with_pagination(fromDate, toDate, 0, []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
d.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ИСПРАВЛЕННАЯ функция загрузки suppliers с пагинацией
|
||||||
|
function load_suppliers_with_pagination(fromDate, toDate, offset = 0, accumulated_data = null) {
|
||||||
|
const maxCount = 200;
|
||||||
|
|
||||||
|
// Инициализируем accumulated_data если это первый вызов
|
||||||
|
if (!accumulated_data) {
|
||||||
|
accumulated_data = {
|
||||||
|
customers_created: 0,
|
||||||
|
suppliers_created: 0,
|
||||||
|
skipped_count: 0,
|
||||||
|
failed_count: 0,
|
||||||
|
total_invoices: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Fetching suppliers from E-Taxes...'),
|
||||||
|
indicator: 'blue'
|
||||||
|
}, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.load_parties_from_invoices',
|
||||||
|
args: {
|
||||||
|
'date_from': fromDate,
|
||||||
|
'date_to': toDate,
|
||||||
|
'max_count': maxCount,
|
||||||
|
'offset': offset,
|
||||||
|
'invoice_type': 'purchase'
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
// Добавляем полученные инвойсы к уже накопленным
|
||||||
|
let currentInvoices = r.message.invoices || [];
|
||||||
|
let allInvoices = accumulated_data.invoices ? accumulated_data.invoices.concat(currentInvoices) : currentInvoices;
|
||||||
|
let hasMore = r.message.hasMore || false;
|
||||||
|
let token = r.message.token;
|
||||||
|
|
||||||
|
// Сохраняем накопленные инвойсы
|
||||||
|
accumulated_data.invoices = allInvoices;
|
||||||
|
accumulated_data.token = token;
|
||||||
|
|
||||||
|
if (hasMore && currentInvoices.length > 0) {
|
||||||
|
// Если есть еще инвойсы, загружаем следующую партию
|
||||||
|
let newOffset = offset + currentInvoices.length;
|
||||||
|
load_suppliers_with_pagination(fromDate, toDate, newOffset, accumulated_data);
|
||||||
|
} else {
|
||||||
|
// Если больше нет инвойсов, переходим к обработке
|
||||||
|
if (allInvoices.length > 0) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Found {0} invoices. Processing suppliers...', [allInvoices.length]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 3);
|
||||||
|
|
||||||
|
// Теперь обрабатываем инвойсы батчами с прогресс баром
|
||||||
|
setTimeout(() => {
|
||||||
|
process_invoices_for_suppliers_with_progress(allInvoices, token);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Information'),
|
||||||
|
indicator: 'blue',
|
||||||
|
message: __('No invoices found for the specified period')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||||
|
function() {
|
||||||
|
check_and_process_token_suppliers(function() {
|
||||||
|
show_invoice_filter_dialog_suppliers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Authentication cancelled'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('An error occurred while loading suppliers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
// ИСПРАВЛЕНО: Очищаем состояние при ошибке
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Network Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to connect to server: ') + (error || 'Unknown error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ИСПРАВЛЕННАЯ функция обработки инвойсов для suppliers с прогресс баром
|
||||||
|
function process_invoices_for_suppliers_with_progress(invoices, token, processedCount = 0, currentIndex = 0, batchSize = 50) {
|
||||||
|
// Инициализация при первом вызове
|
||||||
|
if (processedCount === 0 && currentIndex === 0) {
|
||||||
|
window.cancelSuppliersLoading = false;
|
||||||
|
window.totalSuppliersToProcess = invoices.length;
|
||||||
|
|
||||||
|
// Показываем прогресс бар
|
||||||
|
frappe.show_progress(__('Processing Suppliers'), 0, invoices.length,
|
||||||
|
__('Extracting suppliers from invoices...'), null, true);
|
||||||
|
|
||||||
|
// Добавляем обработчик отмены
|
||||||
|
$(document).off('progress-cancel.loading_suppliers');
|
||||||
|
$(document).on('progress-cancel.loading_suppliers', function() {
|
||||||
|
window.cancelSuppliersLoading = true;
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Cancelling supplier extraction...'),
|
||||||
|
indicator: 'orange'
|
||||||
|
}, 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка отмены
|
||||||
|
if (window.cancelSuppliersLoading) {
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Supplier extraction cancelled'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если все инвойсы обработаны
|
||||||
|
if (currentIndex >= invoices.length) {
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
|
||||||
|
// Показываем итоговый результат
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Suppliers Loaded Successfully'),
|
||||||
|
indicator: 'green',
|
||||||
|
message: __('Created {0} suppliers and {1} customers. Total invoices processed: {2}',
|
||||||
|
[processedCount, 0, invoices.length]) // customers считается на сервере
|
||||||
|
});
|
||||||
|
|
||||||
|
// ИСПРАВЛЕНО: Обновляем список с задержкой для уверенности в очистке состояния
|
||||||
|
setTimeout(function() {
|
||||||
|
if (cur_list) {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем размер текущего батча
|
||||||
|
const remainingInvoices = invoices.length - currentIndex;
|
||||||
|
const currentBatchSize = Math.min(batchSize, remainingInvoices);
|
||||||
|
const currentBatch = invoices.slice(currentIndex, currentIndex + currentBatchSize);
|
||||||
|
|
||||||
|
// Обновляем прогресс
|
||||||
|
frappe.show_progress(__('Processing Suppliers'), currentIndex, invoices.length,
|
||||||
|
__('Processing batch {0}-{1} of {2} invoices', [currentIndex + 1, currentIndex + currentBatchSize, invoices.length]));
|
||||||
|
|
||||||
|
// Обрабатываем текущий батч
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.process_invoice_parties_from_list',
|
||||||
|
args: {
|
||||||
|
'invoice_list_data': currentBatch,
|
||||||
|
'token': token
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
// Обновляем накопленные данные
|
||||||
|
processedCount += (r.message.customers_created || 0) + (r.message.suppliers_created || 0);
|
||||||
|
|
||||||
|
// Переходим к следующему батчу с небольшой задержкой
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelSuppliersLoading) { // Дополнительная проверка
|
||||||
|
process_invoices_for_suppliers_with_progress(invoices, token, processedCount,
|
||||||
|
currentIndex + currentBatchSize, batchSize);
|
||||||
|
}
|
||||||
|
}, 100); // 100ms задержка между батчами
|
||||||
|
|
||||||
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
|
cleanup_suppliers_loading_state();
|
||||||
|
|
||||||
|
frappe.confirm(
|
||||||
|
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||||
|
function() {
|
||||||
|
check_and_process_token_suppliers(function() {
|
||||||
|
// Получаем новый токен и продолжаем с текущего индекса
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.auth.get_default_asan_login',
|
||||||
|
callback: function(login_r) {
|
||||||
|
if (login_r.message && login_r.message.found && login_r.message.main_token) {
|
||||||
|
process_invoices_for_suppliers_with_progress(invoices, login_r.message.main_token,
|
||||||
|
processedCount, currentIndex, batchSize);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint(__('Failed to get authentication token'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Authentication cancelled'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Ошибка обработки батча - продолжаем со следующим
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelSuppliersLoading) { // Дополнительная проверка
|
||||||
|
process_invoices_for_suppliers_with_progress(invoices, token, processedCount,
|
||||||
|
currentIndex + currentBatchSize, batchSize);
|
||||||
|
}
|
||||||
|
}, 200); // Увеличенная задержка при ошибках
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
// Сетевая ошибка - продолжаем со следующим батчом
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!window.cancelSuppliersLoading) { // Дополнительная проверка
|
||||||
|
process_invoices_for_suppliers_with_progress(invoices, token, processedCount,
|
||||||
|
currentIndex + currentBatchSize, batchSize);
|
||||||
|
}
|
||||||
|
}, 500); // Еще большая задержка при сетевых ошибках
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1329,17 +1329,24 @@ SalesETaxes.import = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Обработка результата импорта
|
// Обработка результата импорта - ОБНОВЛЕНО для Sales Order + Sales Invoice
|
||||||
_handleImportResult: function(importR, invoiceMessage, invoiceId, scheduleDate, receiverName, total,
|
_handleImportResult: function(importR, invoiceMessage, invoiceId, scheduleDate, receiverName, total,
|
||||||
invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
|
invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
|
||||||
if (importR.message && importR.message.success) {
|
if (importR.message && importR.message.success) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''),
|
message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''),
|
||||||
indicator: 'green'
|
indicator: 'green'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
|
// ИЗМЕНЕНО: теперь получаем sales_order и sales_invoice из ответа
|
||||||
|
const salesOrderName = importR.message.sales_order;
|
||||||
|
const salesInvoiceName = importR.message.sales_invoice;
|
||||||
|
|
||||||
|
// ИЗМЕНЕНО: создание E-Taxes Sales и связывание с Sales Order происходит на сервере
|
||||||
|
// Поэтому просто увеличиваем счетчик и продолжаем
|
||||||
processedCount++;
|
processedCount++;
|
||||||
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
|
SalesETaxes.import._continueOrFinish(invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice);
|
||||||
|
|
||||||
} else if (importR.message && (importR.message.unmatched_items || importR.message.unmatched_parties || importR.message.unmatched_units)) {
|
} else if (importR.message && (importR.message.unmatched_items || importR.message.unmatched_parties || importR.message.unmatched_units)) {
|
||||||
let errorType = '';
|
let errorType = '';
|
||||||
const additionalData = { invoice_items: invoiceMessage.items || [] };
|
const additionalData = { invoice_items: invoiceMessage.items || [] };
|
||||||
|
|
@ -1490,9 +1497,8 @@ frappe.ui.form.on('Sales Order', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sales Order List
|
|
||||||
frappe.listview_settings['Sales Order'] = {
|
frappe.listview_settings['Sales Order'] = {
|
||||||
add_fields: ['customer', 'transaction_date', 'status', 'docstatus', 'is_taxes_doc'],
|
add_fields: ['customer', 'transaction_date', 'status', 'docstatus', 'is_taxes_doc', 'taxes_doc'],
|
||||||
|
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if (doc.status === 'Closed') {
|
if (doc.status === 'Closed') {
|
||||||
|
|
@ -1511,6 +1517,7 @@ frappe.listview_settings['Sales Order'] = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(listview) {
|
onload: function(listview) {
|
||||||
|
// ДОБАВЛЕНО: кнопка импорта из E-Taxes для Sales Order
|
||||||
listview.page.add_menu_item(__('Import from E-Taxes'), function() {
|
listview.page.add_menu_item(__('Import from E-Taxes'), function() {
|
||||||
SalesETaxes.import.showDialog();
|
SalesETaxes.import.showDialog();
|
||||||
});
|
});
|
||||||
|
|
@ -1550,9 +1557,13 @@ frappe.ui.form.on('Sales Invoice', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sales Invoice List
|
// Sales Invoice List - УБРАНО: кнопка импорта (теперь только в Sales Order)
|
||||||
frappe.listview_settings['Sales Invoice'] = {
|
frappe.listview_settings['Sales Invoice'] = {
|
||||||
|
add_fields: ['customer', 'posting_date', 'status', 'docstatus', 'is_taxes_doc'],
|
||||||
|
|
||||||
onload: function(listview) {
|
onload: function(listview) {
|
||||||
|
// УБРАНО: кнопка импорта - теперь импорт только через Sales Order
|
||||||
|
|
||||||
listview.columns.push({
|
listview.columns.push({
|
||||||
type: 'Check',
|
type: 'Check',
|
||||||
df: {
|
df: {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ doctype_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",
|
||||||
|
"E-Taxes Suppliers": "client/e_taxes_suppliers_list.js",
|
||||||
|
"E-Taxes Customers": "client/e_taxes_customers_list.js",
|
||||||
"E-Taxes Unit": "client/e_taxes_unit_list.js",
|
"E-Taxes Unit": "client/e_taxes_unit_list.js",
|
||||||
"Purchase Order": "client/purchase_order.js",
|
"Purchase Order": "client/purchase_order.js",
|
||||||
"Purchase Invoice": "client/purchase_invoice.js",
|
"Purchase Invoice": "client/purchase_invoice.js",
|
||||||
|
|
@ -28,6 +30,10 @@ doc_events = {
|
||||||
"on_trash": "invoice_az.api.on_delete_purchase_order",
|
"on_trash": "invoice_az.api.on_delete_purchase_order",
|
||||||
"on_cancel": "invoice_az.api.on_delete_purchase_order"
|
"on_cancel": "invoice_az.api.on_delete_purchase_order"
|
||||||
},
|
},
|
||||||
|
"Sales Order": {
|
||||||
|
"on_trash": "invoice_az.sales_api.on_delete_sales_order",
|
||||||
|
"on_cancel": "invoice_az.sales_api.on_delete_sales_order"
|
||||||
|
},
|
||||||
"E-Taxes Settings": {
|
"E-Taxes Settings": {
|
||||||
"on_update": "invoice_az.api.update_mapped_statuses"
|
"on_update": "invoice_az.api.update_mapped_statuses"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2025-07-11 14:00:00",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"etaxes_customer_name",
|
||||||
|
"etaxes_tax_id",
|
||||||
|
"erp_customer",
|
||||||
|
"customer_group",
|
||||||
|
"territory",
|
||||||
|
"payment_terms",
|
||||||
|
"mapping_type"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "E-Taxes Customer Name",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tax ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "erp_customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "ERP Customer",
|
||||||
|
"options": "Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Customer Group",
|
||||||
|
"options": "Customer Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "territory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Territory",
|
||||||
|
"options": "Territory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_terms",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Terms",
|
||||||
|
"options": "Payment Terms Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Manual",
|
||||||
|
"fieldname": "mapping_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Mapping Type",
|
||||||
|
"options": "Manual\nAutomatic",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-07-11 14:00:00",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Invoice Az",
|
||||||
|
"name": "E-Taxes Customer Mappings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
|
|
@ -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 ETaxesCustomerMappings(Document):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright (c) 2025, Jey ERP and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("E-Taxes Customers", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "field:etaxes_party_name",
|
||||||
|
"creation": "2025-07-11 14:00:00",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"etaxes_party_name",
|
||||||
|
"etaxes_tax_id",
|
||||||
|
"etaxes_party_type",
|
||||||
|
"etaxes_address",
|
||||||
|
"source_invoice",
|
||||||
|
"status",
|
||||||
|
"customer_mapping_section",
|
||||||
|
"mapped_customer",
|
||||||
|
"customer_group",
|
||||||
|
"territory",
|
||||||
|
"payment_terms"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_party_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer Name",
|
||||||
|
"length": 500,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tax ID",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Receiver",
|
||||||
|
"fieldname": "etaxes_party_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "E-Taxes Type",
|
||||||
|
"options": "Receiver\nSender/Receiver",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_address",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Address",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Source invoice number",
|
||||||
|
"fieldname": "source_invoice",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Source",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "New",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "New\nMapped\nProcessing",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_mapping_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Customer Mapping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mapped_customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mapped Customer",
|
||||||
|
"options": "Customer",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Customer Group",
|
||||||
|
"options": "Customer Group",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "territory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Territory",
|
||||||
|
"options": "Territory",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_terms",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Terms",
|
||||||
|
"options": "Payment Terms Template",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-07-11 14:00:00",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Invoice Az",
|
||||||
|
"name": "E-Taxes Customers",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
|
|
@ -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 ETaxesCustomers(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 UnitTestETaxesCustomers(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for ETaxesCustomers.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationTestETaxesCustomers(IntegrationTestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for ETaxesCustomers.
|
||||||
|
Use this class for testing interactions between multiple components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
@ -501,6 +501,381 @@ frappe.ui.form.on('E-Taxes Settings', {
|
||||||
}
|
}
|
||||||
}, __('Partners'));
|
}, __('Partners'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// КНОПКИ ДЛЯ CUSTOMERS
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Match customers by similar name'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
match_similar_customers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
match_similar_customers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function match_similar_customers() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('This operation will automatically match unmapped customers with similar names. Continue?'),
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Matching customers...'),
|
||||||
|
indicator: 'blue'
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.match_similar_customers',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Matched {0} of {1} customers',
|
||||||
|
[r.message.matched_count, r.message.total_processed]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
frm.reload_doc();
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error matching customers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, __('Customers'));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Create matching customers'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
create_unmapped_customers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_unmapped_customers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_unmapped_customers() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('<div style="color: red; font-weight: bold;">WARNING! This action will create new customers in the system for all unmapped elements!</div><p>This action is irreversible. Are you sure?</p>'),
|
||||||
|
function() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Are you really sure you want to create new customers? This cannot be undone.'),
|
||||||
|
function() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.create_unmapped_customers',
|
||||||
|
args: {
|
||||||
|
'settings_name': frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Created {0} new customers', [r.message.created_count]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
frm.reload_doc();
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error creating customers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, __('Customers'));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Add unmapped customers'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
add_unmapped_customers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
add_unmapped_customers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_unmapped_customers() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.get_unmapped_customers',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
if (r.message.customers && r.message.customers.length > 0) {
|
||||||
|
r.message.customers.forEach(function(customer) {
|
||||||
|
let row = frm.add_child('customer_mappings');
|
||||||
|
|
||||||
|
row.etaxes_customer_name = customer.etaxes_party_name;
|
||||||
|
row.etaxes_tax_id = customer.etaxes_tax_id;
|
||||||
|
|
||||||
|
if (customer.customer_group)
|
||||||
|
row.customer_group = customer.customer_group;
|
||||||
|
|
||||||
|
if (customer.territory)
|
||||||
|
row.territory = customer.territory;
|
||||||
|
|
||||||
|
if (customer.payment_terms)
|
||||||
|
row.payment_terms = customer.payment_terms;
|
||||||
|
|
||||||
|
row.mapping_type = 'Manual';
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.refresh_field('customer_mappings');
|
||||||
|
|
||||||
|
frm.save().then(function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Added {0} unmapped customers', [r.message.customers.length]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('No unmapped customers'),
|
||||||
|
indicator: 'blue'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error retrieving customers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, __('Customers'));
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// КНОПКИ ДЛЯ SUPPLIERS
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Match suppliers by similar name'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
match_similar_suppliers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
match_similar_suppliers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function match_similar_suppliers() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('This operation will automatically match unmapped suppliers with similar names. Continue?'),
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Matching suppliers...'),
|
||||||
|
indicator: 'blue'
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.match_similar_suppliers',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Matched {0} of {1} suppliers',
|
||||||
|
[r.message.matched_count, r.message.total_processed]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
frm.reload_doc();
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error matching suppliers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, __('Suppliers'));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Create matching suppliers'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
create_unmapped_suppliers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_unmapped_suppliers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_unmapped_suppliers() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('<div style="color: red; font-weight: bold;">WARNING! This action will create new suppliers in the system for all unmapped elements!</div><p>This action is irreversible. Are you sure?</p>'),
|
||||||
|
function() {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Are you really sure you want to create new suppliers? This cannot be undone.'),
|
||||||
|
function() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.create_unmapped_suppliers',
|
||||||
|
args: {
|
||||||
|
'settings_name': frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Created {0} new suppliers', [r.message.created_count]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
frm.reload_doc();
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error creating suppliers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, __('Suppliers'));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Add unmapped suppliers'), function() {
|
||||||
|
if(frm.is_dirty()) {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Document contains unsaved changes. Save before performing the operation?'),
|
||||||
|
function() {
|
||||||
|
frm.save().then(function() {
|
||||||
|
add_unmapped_suppliers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Operation canceled. Please save the document first.'),
|
||||||
|
indicator: 'red'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
add_unmapped_suppliers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_unmapped_suppliers() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.get_unmapped_suppliers',
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
if (r.message.suppliers && r.message.suppliers.length > 0) {
|
||||||
|
r.message.suppliers.forEach(function(supplier) {
|
||||||
|
let row = frm.add_child('supplier_mappings');
|
||||||
|
|
||||||
|
row.etaxes_supplier_name = supplier.etaxes_party_name;
|
||||||
|
row.etaxes_tax_id = supplier.etaxes_tax_id;
|
||||||
|
|
||||||
|
if (supplier.supplier_group)
|
||||||
|
row.supplier_group = supplier.supplier_group;
|
||||||
|
|
||||||
|
if (supplier.payment_terms)
|
||||||
|
row.payment_terms = supplier.payment_terms;
|
||||||
|
|
||||||
|
row.mapping_type = 'Manual';
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.refresh_field('supplier_mappings');
|
||||||
|
|
||||||
|
frm.save().then(function() {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Added {0} unmapped suppliers', [r.message.suppliers.length]),
|
||||||
|
indicator: 'green'
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('No unmapped suppliers'),
|
||||||
|
indicator: 'blue'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error retrieving suppliers')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, __('Suppliers'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Кнопки для единиц измерения
|
// Кнопки для единиц измерения
|
||||||
frm.add_custom_button(__('Match units by similar name'), function() {
|
frm.add_custom_button(__('Match units by similar name'), function() {
|
||||||
if(frm.is_dirty()) {
|
if(frm.is_dirty()) {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@
|
||||||
"mappings_tab",
|
"mappings_tab",
|
||||||
"item_mappings_section",
|
"item_mappings_section",
|
||||||
"item_mappings",
|
"item_mappings",
|
||||||
|
"customer_mappings_section",
|
||||||
|
"customer_mappings",
|
||||||
|
"supplier_mappings_section",
|
||||||
|
"supplier_mappings",
|
||||||
"party_mappings_section",
|
"party_mappings_section",
|
||||||
"party_mappings",
|
"party_mappings",
|
||||||
"unit_mappings_section",
|
"unit_mappings_section",
|
||||||
|
|
@ -157,10 +161,32 @@
|
||||||
"label": "Item Mappings",
|
"label": "Item Mappings",
|
||||||
"options": "E-Taxes Item Mapping"
|
"options": "E-Taxes Item Mapping"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_mappings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Customer Mappings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_mappings",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Customer Mappings",
|
||||||
|
"options": "E-Taxes Customer Mappings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_mappings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Supplier Mappings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_mappings",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Supplier Mappings",
|
||||||
|
"options": "E-Taxes Supplier Mappings"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_mappings_section",
|
"fieldname": "party_mappings_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Business Partner Mappings"
|
"label": "Business Partner Mappings (Legacy)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_mappings",
|
"fieldname": "party_mappings",
|
||||||
|
|
@ -182,7 +208,7 @@
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-21 15:00:00.000000",
|
"modified": "2025-07-11 14:00:00.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Invoice Az",
|
"module": "Invoice Az",
|
||||||
"name": "E-Taxes Settings",
|
"name": "E-Taxes Settings",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2025-07-11 14:00:00",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"etaxes_supplier_name",
|
||||||
|
"etaxes_tax_id",
|
||||||
|
"erp_supplier",
|
||||||
|
"supplier_group",
|
||||||
|
"payment_terms",
|
||||||
|
"mapping_type"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_supplier_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "E-Taxes Supplier Name",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tax ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "erp_supplier",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "ERP Supplier",
|
||||||
|
"options": "Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Supplier Group",
|
||||||
|
"options": "Supplier Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_terms",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Terms",
|
||||||
|
"options": "Payment Terms Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Manual",
|
||||||
|
"fieldname": "mapping_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Mapping Type",
|
||||||
|
"options": "Manual\nAutomatic",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-07-11 14:00:00",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Invoice Az",
|
||||||
|
"name": "E-Taxes Supplier Mappings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
|
|
@ -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 ETaxesSupplierMappings(Document):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright (c) 2025, Jey ERP and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("E-Taxes Suppliers", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "field:etaxes_party_name",
|
||||||
|
"creation": "2025-07-11 14:00:00",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"etaxes_party_name",
|
||||||
|
"etaxes_tax_id",
|
||||||
|
"etaxes_party_type",
|
||||||
|
"etaxes_address",
|
||||||
|
"source_invoice",
|
||||||
|
"status",
|
||||||
|
"supplier_mapping_section",
|
||||||
|
"mapped_supplier",
|
||||||
|
"supplier_group",
|
||||||
|
"payment_terms"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_party_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Supplier Name",
|
||||||
|
"length": 500,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tax ID",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Sender",
|
||||||
|
"fieldname": "etaxes_party_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "E-Taxes Type",
|
||||||
|
"options": "Sender\nSender/Receiver",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "etaxes_address",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Address",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Source invoice number",
|
||||||
|
"fieldname": "source_invoice",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Source",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "New",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "New\nMapped\nProcessing",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_mapping_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Supplier Mapping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mapped_supplier",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mapped Supplier",
|
||||||
|
"options": "Supplier",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Supplier Group",
|
||||||
|
"options": "Supplier Group",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_terms",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Terms",
|
||||||
|
"options": "Payment Terms Template",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-07-11 14:00:00",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Invoice Az",
|
||||||
|
"name": "E-Taxes Suppliers",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
|
|
@ -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 ETaxesSuppliers(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 UnitTestETaxesSuppliers(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for ETaxesSuppliers.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationTestETaxesSuppliers(IntegrationTestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for ETaxesSuppliers.
|
||||||
|
Use this class for testing interactions between multiple components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
@ -214,8 +214,8 @@ def get_sales_invoice_details(token, invoice_id):
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehouse=None):
|
def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, schedule_date=None, warehouse=None):
|
||||||
"""Import sales invoice taking into account mappings and processing unmapped elements"""
|
"""Import sales invoice taking into account NEW customer mappings"""
|
||||||
# Записываем активность
|
# Записываем активность
|
||||||
record_etaxes_activity()
|
record_etaxes_activity()
|
||||||
|
|
||||||
|
|
@ -240,26 +240,24 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
if mapping.etaxes_item_name and mapping.erp_item:
|
if mapping.etaxes_item_name and mapping.erp_item:
|
||||||
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||||
|
|
||||||
party_mappings = {}
|
# ИЗМЕНЕНО: Используем customer_mappings вместо party_mappings
|
||||||
for mapping in settings.party_mappings:
|
customer_mappings = {}
|
||||||
if mapping.etaxes_party_name and mapping.erp_party:
|
for mapping in settings.customer_mappings:
|
||||||
key = f"{mapping.etaxes_party_name.lower()}|{mapping.etaxes_tax_id.lower() if mapping.etaxes_tax_id else ''}"
|
if mapping.etaxes_customer_name and mapping.erp_customer:
|
||||||
party_mappings[key] = (mapping.erp_party, mapping.party_type)
|
key = f"{mapping.etaxes_customer_name.lower()}|{mapping.etaxes_tax_id.lower() if mapping.etaxes_tax_id else ''}"
|
||||||
|
customer_mappings[key] = mapping.erp_customer
|
||||||
|
|
||||||
# Get default warehouse
|
# Get default warehouse
|
||||||
default_warehouse = warehouse
|
default_warehouse = warehouse
|
||||||
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
# If warehouse not specified, try to get from settings
|
|
||||||
if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
|
if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
|
||||||
default_warehouse = settings.default_warehouse
|
default_warehouse = settings.default_warehouse
|
||||||
else:
|
else:
|
||||||
# If warehouse not specified in settings, try to get default warehouse for company
|
|
||||||
company = frappe.defaults.get_user_default('Company')
|
company = frappe.defaults.get_user_default('Company')
|
||||||
if company:
|
if company:
|
||||||
default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
|
default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
|
||||||
|
|
||||||
# If still no warehouse, take first active warehouse
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
warehouses = frappe.get_all('Warehouse',
|
warehouses = frappe.get_all('Warehouse',
|
||||||
filters={'is_group': 0, 'disabled': 0},
|
filters={'is_group': 0, 'disabled': 0},
|
||||||
|
|
@ -268,56 +266,54 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
if warehouses:
|
if warehouses:
|
||||||
default_warehouse = warehouses[0].name
|
default_warehouse = warehouses[0].name
|
||||||
|
|
||||||
# Check warehouse existence
|
|
||||||
if not default_warehouse:
|
if not default_warehouse:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create new Sales Invoice
|
# ИЗМЕНЕНО: Создаем Sales Order сначала (аналогично Purchase Order логике)
|
||||||
si = frappe.new_doc('Sales Invoice')
|
if sales_order_name:
|
||||||
|
so = frappe.get_doc('Sales Order', sales_order_name)
|
||||||
# Set customer
|
else:
|
||||||
receiver = invoice_data.get('receiver', {})
|
so = frappe.new_doc('Sales Order')
|
||||||
receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}"
|
|
||||||
|
# ИЗМЕНЕНО: Set customer используя customer_mappings
|
||||||
if receiver_key in party_mappings and party_mappings[receiver_key][0]:
|
receiver = invoice_data.get('receiver', {})
|
||||||
# Check if it's a customer
|
receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}"
|
||||||
if party_mappings[receiver_key][1] == 'Customer':
|
|
||||||
si.customer = party_mappings[receiver_key][0]
|
if receiver_key in customer_mappings and customer_mappings[receiver_key]:
|
||||||
|
so.customer = customer_mappings[receiver_key]
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Receiver is mapped as {party_mappings[receiver_key][1]}, but Sales Invoice requires a Customer'
|
'unmatched_parties': [{
|
||||||
|
'name': receiver.get('name', ''),
|
||||||
|
'tin': receiver.get('tin', ''),
|
||||||
|
'type': 'Receiver'
|
||||||
|
}],
|
||||||
|
'message': f'No mapping found for customer: {receiver.get("name", "")}'
|
||||||
}
|
}
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'unmatched_parties': [{
|
|
||||||
'name': receiver.get('name', ''),
|
|
||||||
'tin': receiver.get('tin', ''),
|
|
||||||
'type': 'Receiver'
|
|
||||||
}],
|
|
||||||
'message': f'No mapping found for customer: {receiver.get("name", "")}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set dates
|
|
||||||
si.posting_date = frappe.utils.today()
|
|
||||||
|
|
||||||
# Get date from invoice or parameter if specified
|
|
||||||
if schedule_date:
|
|
||||||
si.due_date = schedule_date
|
|
||||||
elif invoice_data.get("creationDate"):
|
|
||||||
si.due_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
|
||||||
else:
|
|
||||||
si.due_date = frappe.utils.add_days(frappe.utils.today(), 30)
|
|
||||||
|
|
||||||
si.company = frappe.defaults.get_user_default('Company')
|
# Set dates
|
||||||
|
so.transaction_date = frappe.utils.today()
|
||||||
|
|
||||||
|
if schedule_date:
|
||||||
|
so.delivery_date = schedule_date
|
||||||
|
elif invoice_data.get("creationDate"):
|
||||||
|
so.delivery_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
||||||
|
else:
|
||||||
|
so.delivery_date = frappe.utils.today()
|
||||||
|
|
||||||
|
so.company = frappe.defaults.get_user_default('Company')
|
||||||
|
|
||||||
# Add additional fields from invoice
|
# Add/update additional fields from invoice
|
||||||
si.title = f"Sales Invoice {invoice_data.get('serialNumber', '')}"
|
so.title = f"Sales Order {invoice_data.get('serialNumber', '')}"
|
||||||
si.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
so.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
||||||
|
|
||||||
|
# Clear existing items if need to update them completely
|
||||||
|
if sales_order_name and invoice_data.get("items"):
|
||||||
|
so.items = []
|
||||||
|
|
||||||
date_to_use = None
|
date_to_use = None
|
||||||
if schedule_date:
|
if schedule_date:
|
||||||
|
|
@ -360,7 +356,6 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
break
|
break
|
||||||
|
|
||||||
if not mapped_item:
|
if not mapped_item:
|
||||||
# Если соответствие не найдено, добавляем в список несопоставленных
|
|
||||||
unmatched_items.append({
|
unmatched_items.append({
|
||||||
'name': item_name,
|
'name': item_name,
|
||||||
'code': item_code
|
'code': item_code
|
||||||
|
|
@ -372,7 +367,6 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
mapped_uom = None
|
mapped_uom = None
|
||||||
|
|
||||||
if unit_name:
|
if unit_name:
|
||||||
# Ищем существующую запись E-Taxes Unit и берем mapped_unit
|
|
||||||
try:
|
try:
|
||||||
mapped_uom = frappe.db.get_value('E-Taxes Unit',
|
mapped_uom = frappe.db.get_value('E-Taxes Unit',
|
||||||
filters={'etaxes_unit_name': unit_name},
|
filters={'etaxes_unit_name': unit_name},
|
||||||
|
|
@ -383,46 +377,41 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
# Если соответствие единицы не найдено, используем UOM из товара или настроек
|
# Если соответствие единицы не найдено, используем UOM из товара или настроек
|
||||||
if not mapped_uom:
|
if not mapped_uom:
|
||||||
if unit_name:
|
if unit_name:
|
||||||
# Добавляем в список несопоставленных единиц
|
|
||||||
unmatched_units.append({
|
unmatched_units.append({
|
||||||
'name': unit_name
|
'name': unit_name
|
||||||
})
|
})
|
||||||
|
|
||||||
# Используем UOM из настроек товара или default
|
|
||||||
try:
|
try:
|
||||||
item_doc = frappe.get_doc('Item', mapped_item)
|
item_doc = frappe.get_doc('Item', mapped_item)
|
||||||
mapped_uom = item_doc.stock_uom
|
mapped_uom = item_doc.stock_uom
|
||||||
except:
|
except:
|
||||||
# Fallback на default_uom из настроек
|
|
||||||
mapped_uom = settings.default_uom if settings.default_uom else "Nos"
|
mapped_uom = settings.default_uom if settings.default_uom else "Nos"
|
||||||
|
|
||||||
# Add position to Sales Invoice
|
# ИЗМЕНЕНО: Add position to Sales Order (не Sales Invoice)
|
||||||
si_item = frappe.new_doc("Sales Invoice Item")
|
so_item = frappe.new_doc("Sales Order Item")
|
||||||
si_item.parent = si.name
|
so_item.parent = so.name
|
||||||
si_item.parenttype = "Sales Invoice"
|
so_item.parenttype = "Sales Order"
|
||||||
si_item.parentfield = "items"
|
so_item.parentfield = "items"
|
||||||
|
|
||||||
si_item.item_code = mapped_item
|
so_item.item_code = mapped_item
|
||||||
|
|
||||||
# Получаем название и описание из базы данных Item
|
# Получаем название и описание из базы данных Item
|
||||||
try:
|
try:
|
||||||
item_doc = frappe.get_doc('Item', mapped_item)
|
item_doc = frappe.get_doc('Item', mapped_item)
|
||||||
si_item.item_name = item_doc.item_name
|
so_item.item_name = item_doc.item_name
|
||||||
si_item.description = item_doc.description or item_doc.item_name
|
so_item.description = item_doc.description or item_doc.item_name
|
||||||
except:
|
except:
|
||||||
# Fallback на название из E-Taxes если не удалось получить из базы
|
so_item.item_name = item.get("productName", "")
|
||||||
si_item.item_name = item.get("productName", "")
|
so_item.description = item.get("productName", "")
|
||||||
si_item.description = item.get("productName", "")
|
|
||||||
|
|
||||||
si_item.qty = item.get("quantity", 0)
|
so_item.qty = item.get("quantity", 0)
|
||||||
si_item.rate = item.get("pricePerUnit", 0)
|
so_item.rate = item.get("pricePerUnit", 0)
|
||||||
si_item.amount = item.get("cost", 0)
|
so_item.amount = item.get("cost", 0)
|
||||||
si_item.uom = mapped_uom
|
so_item.uom = mapped_uom
|
||||||
|
so_item.delivery_date = date_to_use # ИЗМЕНЕНО: delivery_date вместо schedule_date
|
||||||
# IMPORTANT: Set default warehouse
|
so_item.warehouse = default_warehouse
|
||||||
si_item.warehouse = default_warehouse
|
|
||||||
|
|
||||||
si.append("items", si_item)
|
so.append("items", so_item)
|
||||||
added_items_count += 1
|
added_items_count += 1
|
||||||
|
|
||||||
# Check if there are unmapped items
|
# Check if there are unmapped items
|
||||||
|
|
@ -444,31 +433,28 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
# Check that there is at least one element in table
|
# Check that there is at least one element in table
|
||||||
if added_items_count == 0:
|
if added_items_count == 0:
|
||||||
frappe.log_error(
|
frappe.log_error(
|
||||||
f"No items were added to Sales Invoice. Invoice data: {invoice_data}",
|
f"No items were added to Sales Order. Invoice data: {invoice_data}",
|
||||||
"Import Sales Invoice Error"
|
"Import Sales Invoice Error"
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Could not add any items to sales invoice'
|
'message': 'Could not add any items to sales order'
|
||||||
}
|
}
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Создание строки ƏDV в Sales Taxes and Charges ТОЛЬКО если есть vat в данных
|
# ДОБАВЛЕНО: Создание строки ƏDV в Sales Taxes and Charges ТОЛЬКО если есть vat в данных
|
||||||
try:
|
try:
|
||||||
# Получаем VAT из E-Taxes данных
|
|
||||||
vat_amount = invoice_data.get('vat', 0)
|
vat_amount = invoice_data.get('vat', 0)
|
||||||
|
|
||||||
# Создаем строку ƏDV ТОЛЬКО если есть vat в данных и он больше 0
|
|
||||||
if vat_amount and vat_amount > 0:
|
if vat_amount and vat_amount > 0:
|
||||||
vat_account = frappe.db.get_value('Account',
|
vat_account = frappe.db.get_value('Account',
|
||||||
filters={'account_number': '531.1'},
|
filters={'account_number': '521.3'},
|
||||||
fieldname='name')
|
fieldname='name')
|
||||||
|
|
||||||
if vat_account:
|
if vat_account:
|
||||||
# Рассчитываем net_total для определения ставки
|
|
||||||
net_total = sum(item.get('cost', 0) for item in invoice_data.get('items', []))
|
net_total = sum(item.get('cost', 0) for item in invoice_data.get('items', []))
|
||||||
vat_rate = (vat_amount / net_total * 100) if net_total > 0 else 0
|
vat_rate = (vat_amount / net_total * 100) if net_total > 0 else 0
|
||||||
|
|
||||||
si.append("taxes", {
|
so.append("taxes", {
|
||||||
"account_head": vat_account,
|
"account_head": vat_account,
|
||||||
"charge_type": "On Net Total",
|
"charge_type": "On Net Total",
|
||||||
"rate": vat_rate,
|
"rate": vat_rate,
|
||||||
|
|
@ -477,17 +463,27 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
"add_deduct_tax": "Add"
|
"add_deduct_tax": "Add"
|
||||||
})
|
})
|
||||||
|
|
||||||
frappe.log_error(f"Added ƏDV tax: Amount={vat_amount}, Rate={vat_rate}%", "Sales Invoice Tax")
|
frappe.log_error(f"Added ƏDV tax: Amount={vat_amount}, Rate={vat_rate}%", "Sales Order Tax")
|
||||||
else:
|
else:
|
||||||
frappe.log_error("ƏDV account (531.1) not found", "Sales Invoice Tax Warning")
|
frappe.log_error("ƏDV account (521.3) not found", "Sales Order Tax Warning")
|
||||||
else:
|
else:
|
||||||
frappe.log_error("No VAT amount in E-Taxes data, skipping ƏDV tax creation", "Sales Invoice Tax Info")
|
frappe.log_error("No VAT amount in E-Taxes data, skipping ƏDV tax creation", "Sales Order Tax Info")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Sales Invoice Tax Error")
|
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Sales Order Tax Error")
|
||||||
|
|
||||||
# Save document
|
# Save document
|
||||||
si.save()
|
so.save()
|
||||||
|
|
||||||
|
# Additional check that all delivery_date and warehouse are set
|
||||||
|
for item in so.items:
|
||||||
|
if not item.delivery_date:
|
||||||
|
item.delivery_date = date_to_use
|
||||||
|
if not item.warehouse:
|
||||||
|
item.warehouse = default_warehouse
|
||||||
|
|
||||||
|
# Save again to ensure changes are applied
|
||||||
|
so.save()
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
||||||
invoice_id = invoice_data.get('id', '')
|
invoice_id = invoice_data.get('id', '')
|
||||||
|
|
@ -499,27 +495,50 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
||||||
etaxes_sales_result = create_etaxes_sales(invoice_id, date_to_use, receiver_name, total)
|
etaxes_sales_result = create_etaxes_sales(invoice_id, date_to_use, receiver_name, total)
|
||||||
if etaxes_sales_result and etaxes_sales_result.get('success'):
|
if etaxes_sales_result and etaxes_sales_result.get('success'):
|
||||||
# Устанавливаем поля E-Taxes ДО submit'а
|
# Устанавливаем поля E-Taxes ДО submit'а
|
||||||
si.is_taxes_doc = 1
|
so.is_taxes_doc = 1
|
||||||
si.taxes_doc = etaxes_sales_result.get('name')
|
so.taxes_doc = etaxes_sales_result.get('name')
|
||||||
# Сохраняем изменения
|
# Сохраняем изменения
|
||||||
si.save()
|
so.save()
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Invoice
|
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Order
|
||||||
try:
|
try:
|
||||||
si.submit()
|
so.submit()
|
||||||
frappe.log_error(f"Sales Invoice {si.name} submitted successfully", "Import Sales Invoice Success")
|
frappe.log_error(f"Sales Order {so.name} submitted successfully", "Import Sales Invoice Success")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error submitting Sales Invoice {si.name}: {str(e)}", "Submit SI Error")
|
frappe.log_error(f"Error submitting Sales Order {so.name}: {str(e)}", "Submit SO Error")
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Failed to submit Sales Invoice: {str(e)}'
|
'message': f'Failed to submit Sales Order: {str(e)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
# ИЗМЕНЕНИЕ: Создаем Sales Invoice из Sales Order
|
||||||
'success': True,
|
try:
|
||||||
'message': 'Sales invoice data imported successfully.',
|
si_name = create_sales_invoice_from_order(so.name)
|
||||||
'sales_invoice': si.name
|
if si_name:
|
||||||
}
|
# Делаем Submit для Sales Invoice
|
||||||
|
si = frappe.get_doc("Sales Invoice", si_name)
|
||||||
|
si.submit()
|
||||||
|
frappe.log_error(f"Sales Invoice {si_name} created and submitted successfully", "Import Sales Invoice Success")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': 'Sales invoice data imported successfully. Sales Order and Sales Invoice created.',
|
||||||
|
'sales_order': so.name,
|
||||||
|
'sales_invoice': si_name
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': 'Sales invoice data imported successfully. Sales Order created, but Sales Invoice creation failed.',
|
||||||
|
'sales_order': so.name
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error creating or submitting Sales Invoice: {str(e)}", "Submit SI Error")
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': 'Sales invoice data imported successfully. Sales Order created, but Sales Invoice creation failed.',
|
||||||
|
'sales_order': so.name
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error in import_sales_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Sales Invoice Error")
|
frappe.log_error(f"Error in import_sales_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Sales Invoice Error")
|
||||||
|
|
@ -683,3 +702,19 @@ def normalize_string(text, consider_azeri=True):
|
||||||
text = ' '.join(text.split())
|
text = ' '.join(text.split())
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def on_delete_sales_order(doc, method):
|
||||||
|
"""Deletes related E-Taxes Sales record when Sales Order is deleted"""
|
||||||
|
if doc.is_taxes_doc and doc.taxes_doc:
|
||||||
|
try:
|
||||||
|
frappe.logger().info(f"Deleting E-Taxes Sales {doc.taxes_doc} due to Sales Order {doc.name} deletion")
|
||||||
|
|
||||||
|
# Check if E-Taxes Sales record exists
|
||||||
|
if frappe.db.exists("E-Taxes Sales", doc.taxes_doc):
|
||||||
|
frappe.delete_doc('E-Taxes Sales', doc.taxes_doc, ignore_permissions=True, force=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
frappe.logger().info(f"Successfully deleted E-Taxes Sales {doc.taxes_doc}")
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error deleting E-Taxes Sales {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}",
|
||||||
|
"Sales Order Delete Error")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue