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 с прогресс баром
|
||||
frappe.listview_settings['E-Taxes Parties'] = {
|
||||
add_fields: ['status', 'mapped_party'],
|
||||
// Настройки списка E-Taxes Customers с прогресс баром
|
||||
frappe.listview_settings['E-Taxes Customers'] = {
|
||||
add_fields: ['status', 'mapped_customer'],
|
||||
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status === 'Mapped') {
|
||||
|
|
@ -13,48 +13,48 @@ frappe.listview_settings['E-Taxes Parties'] = {
|
|||
},
|
||||
|
||||
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() {
|
||||
// Сначала проверяем актуальность токена перед загрузкой
|
||||
check_and_process_token_parties(function() {
|
||||
show_invoice_filter_dialog();
|
||||
check_and_process_token_customers(function() {
|
||||
show_invoice_filter_dialog_customers();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Глобальная переменная для хранения диалога загрузки
|
||||
var loading_dialog_parties = null;
|
||||
var loading_dialog_customers = null;
|
||||
|
||||
// Очистка состояния при закрытии/перезагрузке страницы
|
||||
$(window).on('beforeunload', function() {
|
||||
cleanup_parties_loading_state();
|
||||
cleanup_customers_loading_state();
|
||||
});
|
||||
|
||||
// Очистка состояния при переходе на другую страницу
|
||||
$(document).on('page:before-change', function() {
|
||||
cleanup_parties_loading_state();
|
||||
cleanup_customers_loading_state();
|
||||
});
|
||||
|
||||
// НОВАЯ ФУНКЦИЯ: Очистка состояния загрузки контрагентов
|
||||
function cleanup_parties_loading_state() {
|
||||
// НОВАЯ ФУНКЦИЯ: Очистка состояния загрузки customers
|
||||
function cleanup_customers_loading_state() {
|
||||
try {
|
||||
// Скрываем прогресс бар
|
||||
frappe.hide_progress();
|
||||
|
||||
// Удаляем event listeners
|
||||
$(document).off('progress-cancel.loading_parties');
|
||||
$(document).off('progress-cancel.loading_customers');
|
||||
|
||||
// Очищаем глобальные переменные
|
||||
if (typeof window.cancelPartiesLoading !== 'undefined') {
|
||||
delete window.cancelPartiesLoading;
|
||||
if (typeof window.cancelCustomersLoading !== 'undefined') {
|
||||
delete window.cancelCustomersLoading;
|
||||
}
|
||||
if (typeof window.totalPartiesToProcess !== 'undefined') {
|
||||
delete window.totalPartiesToProcess;
|
||||
if (typeof window.totalCustomersToProcess !== 'undefined') {
|
||||
delete window.totalCustomersToProcess;
|
||||
}
|
||||
|
||||
// ВАЖНО: Скрываем все возможные диалоги загрузки
|
||||
hide_loading_dialog_parties();
|
||||
hide_loading_dialog_customers();
|
||||
|
||||
// Дополнительная очистка - удаляем все модальные окна, которые могут висеть
|
||||
$('.modal-backdrop').remove();
|
||||
|
|
@ -63,9 +63,9 @@ function cleanup_parties_loading_state() {
|
|||
// Очищаем любые блокировки интерфейса
|
||||
frappe.dom.unfreeze();
|
||||
|
||||
console.log('Parties loading state cleaned up');
|
||||
console.log('Customers loading state cleaned up');
|
||||
} catch (e) {
|
||||
console.error('Error during parties cleanup:', e);
|
||||
console.error('Error during customers cleanup:', e);
|
||||
// В крайнем случае, принудительно разблокируем интерфейс
|
||||
frappe.dom.unfreeze();
|
||||
$('.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) {
|
||||
$('#loading_title_parties').text(title);
|
||||
$('#loading_message_parties').text(message);
|
||||
if (loading_dialog_customers) {
|
||||
$('#loading_title_customers').text(title);
|
||||
$('#loading_message_customers').text(message);
|
||||
if (submessage) {
|
||||
$('#loading_submessage_parties').text(submessage).show();
|
||||
$('#loading_submessage_customers').text(submessage).show();
|
||||
} else {
|
||||
$('#loading_submessage_parties').hide();
|
||||
$('#loading_submessage_customers').hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем диалог с анимацией загрузки
|
||||
loading_dialog_parties = new frappe.ui.Dialog({
|
||||
loading_dialog_customers = new frappe.ui.Dialog({
|
||||
title: title,
|
||||
fields: [
|
||||
{
|
||||
|
|
@ -106,13 +106,13 @@ function show_loading_dialog_parties(title, message, submessage) {
|
|||
}
|
||||
</style>
|
||||
<div class="mt-3">
|
||||
<h4 id="loading_title_parties">${title}</h4>
|
||||
<p id="loading_message_parties">${message}</p>
|
||||
<p id="loading_submessage_parties" style="${submessage ? '' : 'display:none'}">${submessage || ''}</p>
|
||||
<h4 id="loading_title_customers">${title}</h4>
|
||||
<p id="loading_message_customers">${message}</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;">
|
||||
<span id="verification_code_parties">----</span>
|
||||
<span id="verification_code_customers">----</span>
|
||||
</div>
|
||||
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
||||
</div>
|
||||
|
|
@ -123,63 +123,63 @@ function show_loading_dialog_parties(title, message, submessage) {
|
|||
]
|
||||
});
|
||||
|
||||
loading_dialog_parties.show();
|
||||
loading_dialog_parties.$wrapper.find('.modal-dialog').css('max-width', '450px');
|
||||
loading_dialog_customers.show();
|
||||
loading_dialog_customers.$wrapper.find('.modal-dialog').css('max-width', '450px');
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕННАЯ функция для скрытия диалога загрузки
|
||||
function hide_loading_dialog_parties() {
|
||||
if (loading_dialog_parties) {
|
||||
function hide_loading_dialog_customers() {
|
||||
if (loading_dialog_customers) {
|
||||
try {
|
||||
loading_dialog_parties.hide();
|
||||
loading_dialog_parties = null;
|
||||
loading_dialog_customers.hide();
|
||||
loading_dialog_customers = null;
|
||||
} catch (e) {
|
||||
console.error('Error hiding loading dialog parties:', e);
|
||||
loading_dialog_parties = null;
|
||||
console.error('Error hiding loading dialog customers:', e);
|
||||
loading_dialog_customers = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для обновления сообщения в диалоге загрузки
|
||||
function update_loading_message_parties(message, submessage) {
|
||||
if (loading_dialog_parties) {
|
||||
$('#loading_message_parties').text(message);
|
||||
function update_loading_message_customers(message, submessage) {
|
||||
if (loading_dialog_customers) {
|
||||
$('#loading_message_customers').text(message);
|
||||
if (submessage !== undefined) {
|
||||
$('#loading_submessage_parties').text(submessage);
|
||||
$('#loading_submessage_parties').toggle(!!submessage);
|
||||
$('#loading_submessage_customers').text(submessage);
|
||||
$('#loading_submessage_customers').toggle(!!submessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для изменения статуса загрузки на успешный с автоматическим закрытием
|
||||
function set_loading_success_parties(message, submessage, callback, delay = 2000) {
|
||||
if (loading_dialog_parties) {
|
||||
function set_loading_success_customers(message, submessage, callback, delay = 2000) {
|
||||
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;">
|
||||
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Обновляем сообщения
|
||||
$('#loading_message_parties').text(message);
|
||||
$('#loading_message_customers').text(message);
|
||||
if (submessage !== undefined) {
|
||||
$('#loading_submessage_parties').text(submessage);
|
||||
$('#loading_submessage_parties').toggle(!!submessage);
|
||||
$('#loading_submessage_customers').text(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_parties.set_secondary_action(null);
|
||||
loading_dialog_customers.set_primary_action(null);
|
||||
loading_dialog_customers.set_secondary_action(null);
|
||||
|
||||
// Автоматически закрываем через указанную задержку
|
||||
setTimeout(function() {
|
||||
if (loading_dialog_parties) {
|
||||
loading_dialog_parties.hide();
|
||||
loading_dialog_parties = null;
|
||||
if (loading_dialog_customers) {
|
||||
loading_dialog_customers.hide();
|
||||
loading_dialog_customers = null;
|
||||
}
|
||||
if (callback) callback();
|
||||
}, 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) {
|
||||
if (loading_dialog_parties) {
|
||||
function set_loading_error_customers(message, submessage, show_close_button = true) {
|
||||
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;">
|
||||
<i class="fa fa-times" style="font-size: 48px; color: #d9534f;"></i>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Обновляем сообщения
|
||||
$('#loading_message_parties').text(message);
|
||||
$('#loading_message_customers').text(message);
|
||||
if (submessage !== undefined) {
|
||||
$('#loading_submessage_parties').text(submessage);
|
||||
$('#loading_submessage_parties').toggle(!!submessage);
|
||||
$('#loading_submessage_customers').text(submessage);
|
||||
$('#loading_submessage_customers').toggle(!!submessage);
|
||||
}
|
||||
|
||||
// Скрываем код верификации при ошибке
|
||||
$('#verification_code_container_parties').hide();
|
||||
$('#verification_code_container_customers').hide();
|
||||
|
||||
// При необходимости добавляем кнопку закрытия
|
||||
if (show_close_button) {
|
||||
loading_dialog_parties.set_primary_action(__('Close'), function() {
|
||||
hide_loading_dialog_parties();
|
||||
loading_dialog_customers.set_primary_action(__('Close'), function() {
|
||||
hide_loading_dialog_customers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для проверки токена и обработки аутентификации при необходимости
|
||||
function check_and_process_token_parties(callback) {
|
||||
function check_and_process_token_customers(callback) {
|
||||
// Проверяем, доступен ли новый модуль ETaxes
|
||||
if (typeof ETaxes !== 'undefined' && ETaxes.auth) {
|
||||
ETaxes.auth.checkAndProcess(callback);
|
||||
|
|
@ -236,7 +236,7 @@ function check_and_process_token_parties(callback) {
|
|||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired or authentication is required. Would you like to authenticate now?'),
|
||||
function() {
|
||||
start_authentication_process_parties(callback);
|
||||
start_authentication_process_customers(callback);
|
||||
},
|
||||
function() {
|
||||
frappe.show_alert({
|
||||
|
|
@ -258,8 +258,8 @@ function check_and_process_token_parties(callback) {
|
|||
}
|
||||
|
||||
// Функция для запуска полного процесса аутентификации
|
||||
function start_authentication_process_parties(callback) {
|
||||
show_loading_dialog_parties(
|
||||
function start_authentication_process_customers(callback) {
|
||||
show_loading_dialog_customers(
|
||||
__('Starting Authentication'),
|
||||
__('Getting Asan Login settings...'),
|
||||
__('Please wait')
|
||||
|
|
@ -270,7 +270,7 @@ function start_authentication_process_parties(callback) {
|
|||
callback: function(r) {
|
||||
if (r.message && r.message.found) {
|
||||
var asan_login_name = r.message.name;
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Sending authentication request...'),
|
||||
__('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) {
|
||||
$('#verification_code_parties').text(r.message.verification_code);
|
||||
$('#verification_code_container_parties').attr('style', 'display: block !important; margin-top: 15px;');
|
||||
$('#verification_code_customers').text(r.message.verification_code);
|
||||
$('#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'),
|
||||
__('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) {
|
||||
// Если аутентификация успешна, продолжаем процесс
|
||||
complete_authentication_parties(asan_login_name, callback);
|
||||
complete_authentication_customers(asan_login_name, callback);
|
||||
} else {
|
||||
// Если аутентификация не удалась, показываем сообщение об ошибке
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Authentication Failed'),
|
||||
__('Could not authenticate with Asan Imza')
|
||||
);
|
||||
|
|
@ -312,7 +312,7 @@ function start_authentication_process_parties(callback) {
|
|||
});
|
||||
} else {
|
||||
// Если запрос не удался, показываем сообщение об ошибке
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Authentication Error'),
|
||||
r.message ? r.message.message : __('Authentication request failed')
|
||||
);
|
||||
|
|
@ -321,7 +321,7 @@ function start_authentication_process_parties(callback) {
|
|||
});
|
||||
} else {
|
||||
// Если настройки не найдены, показываем сообщение об ошибке
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('No Asan Login Settings'),
|
||||
__('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;
|
||||
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) {
|
||||
// Показываем сообщение о таймауте
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Authentication Timeout'),
|
||||
__('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++;
|
||||
|
||||
// Обновляем счетчик попыток в сообщении
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Waiting for confirmation on your phone'),
|
||||
__('Please check your phone and confirm the authentication request') +
|
||||
' (' + attempts + '/' + maxAttempts + ')'
|
||||
|
|
@ -373,7 +373,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
|||
if (r.message.authenticated) {
|
||||
// Авторизация успешна
|
||||
stopPolling = true;
|
||||
set_loading_success_parties(
|
||||
set_loading_success_customers(
|
||||
__('Authentication Successful'),
|
||||
__('You are now authenticated with Asan Imza'),
|
||||
function() {
|
||||
|
|
@ -387,7 +387,7 @@ function poll_authentication_status_parties(asan_login_name, bearer_token, callb
|
|||
} else {
|
||||
// Если ошибка, останавливаем опрос и показываем сообщение
|
||||
stopPolling = true;
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Authentication Error'),
|
||||
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) {
|
||||
show_loading_dialog_parties(
|
||||
function complete_authentication_customers(asan_login_name, callback) {
|
||||
show_loading_dialog_customers(
|
||||
__('Completing Authentication'),
|
||||
__('Getting certificates...'),
|
||||
__('Please wait')
|
||||
|
|
@ -422,7 +422,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
|||
},
|
||||
callback: function(cert_r) {
|
||||
if (cert_r.message && cert_r.message.success) {
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Certificates retrieved'),
|
||||
__('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_name = r.message.selected_certificate;
|
||||
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Selecting certificate'),
|
||||
cert_name
|
||||
);
|
||||
|
|
@ -455,7 +455,7 @@ function complete_authentication_parties(asan_login_name, callback) {
|
|||
},
|
||||
callback: function(select_r) {
|
||||
if (select_r.message && select_r.message.success) {
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Selecting taxpayer'),
|
||||
__('Using certificate: ') + cert_name
|
||||
);
|
||||
|
|
@ -468,37 +468,37 @@ function complete_authentication_parties(asan_login_name, callback) {
|
|||
},
|
||||
callback: function(tax_r) {
|
||||
if (tax_r.message && tax_r.message.success) {
|
||||
set_loading_success_parties(
|
||||
set_loading_success_customers(
|
||||
__('Authentication Complete'),
|
||||
__('You can now access E-Taxes services'),
|
||||
callback,
|
||||
2000 // Автоматически закроется через 2 секунды
|
||||
);
|
||||
} else {
|
||||
hide_loading_dialog_parties();
|
||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
||||
hide_loading_dialog_customers();
|
||||
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
hide_loading_dialog_parties();
|
||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
||||
hide_loading_dialog_customers();
|
||||
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error parsing certificate:', e);
|
||||
hide_loading_dialog_parties();
|
||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
||||
hide_loading_dialog_customers();
|
||||
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||
}
|
||||
} else {
|
||||
hide_loading_dialog_parties();
|
||||
show_certificate_selector_parties(cert_r.message.certificates, asan_login_name, callback);
|
||||
hide_loading_dialog_customers();
|
||||
show_certificate_selector_customers(cert_r.message.certificates, asan_login_name, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Error'),
|
||||
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) {
|
||||
frappe.msgprint(__('No certificates available'));
|
||||
return;
|
||||
|
|
@ -536,7 +536,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
'<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-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>';
|
||||
});
|
||||
|
||||
|
|
@ -554,7 +554,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
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 cert = certificates[index];
|
||||
|
||||
|
|
@ -571,7 +571,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
dialog.hide();
|
||||
|
||||
// Показываем прогресс
|
||||
show_loading_dialog_parties(
|
||||
show_loading_dialog_customers(
|
||||
__('Selecting Certificate'),
|
||||
__('Processing your selection...'),
|
||||
certName
|
||||
|
|
@ -587,7 +587,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
},
|
||||
callback: function(r) {
|
||||
if (r.message && r.message.success) {
|
||||
update_loading_message_parties(
|
||||
update_loading_message_customers(
|
||||
__('Certificate selected'),
|
||||
__('Now selecting taxpayer information')
|
||||
);
|
||||
|
|
@ -600,14 +600,14 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
},
|
||||
callback: function(r) {
|
||||
if (r.message && r.message.success) {
|
||||
set_loading_success_parties(
|
||||
set_loading_success_customers(
|
||||
__('Authentication Complete'),
|
||||
__('You can now access E-Taxes services'),
|
||||
callback,
|
||||
2000 // Автоматически закроется через 2 секунды
|
||||
);
|
||||
} else {
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Error'),
|
||||
r.message ? r.message.message : __('Failed to select taxpayer')
|
||||
);
|
||||
|
|
@ -615,7 +615,7 @@ function show_certificate_selector_parties(certificates, asan_login_name, callba
|
|||
}
|
||||
});
|
||||
} else {
|
||||
set_loading_error_parties(
|
||||
set_loading_error_customers(
|
||||
__('Error'),
|
||||
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 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: __('Party Filters'),
|
||||
title: __('Customer Filters'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'date_range_section',
|
||||
|
|
@ -652,7 +652,7 @@ function show_invoice_filter_dialog() {
|
|||
default: endDate
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Load Parties'),
|
||||
primary_action_label: __('Load Customers'),
|
||||
primary_action: function() {
|
||||
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');
|
||||
|
||||
// Запускаем загрузку с прогресс-баром
|
||||
load_parties_with_pagination(fromDate, toDate, 0, []);
|
||||
load_customers_with_pagination(fromDate, toDate, 0, []);
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕННАЯ функция загрузки контрагентов с пагинацией
|
||||
function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_data = null) {
|
||||
// ИСПРАВЛЕННАЯ функция загрузки customers с пагинацией
|
||||
function load_customers_with_pagination(fromDate, toDate, offset = 0, accumulated_data = null) {
|
||||
const maxCount = 200;
|
||||
|
||||
// Инициализируем accumulated_data если это первый вызов
|
||||
if (!accumulated_data) {
|
||||
accumulated_data = {
|
||||
created_count: 0,
|
||||
customers_created: 0,
|
||||
suppliers_created: 0,
|
||||
skipped_count: 0,
|
||||
failed_count: 0,
|
||||
total_invoices: 0,
|
||||
unique_parties: 0
|
||||
total_invoices: 0
|
||||
};
|
||||
|
||||
frappe.show_alert({
|
||||
message: __('Fetching parties from E-Taxes...'),
|
||||
message: __('Fetching customers from E-Taxes...'),
|
||||
indicator: 'blue'
|
||||
}, 3);
|
||||
}
|
||||
|
|
@ -727,18 +727,18 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
|||
if (hasMore && currentInvoices.length > 0) {
|
||||
// Если есть еще инвойсы, загружаем следующую партию
|
||||
let newOffset = offset + currentInvoices.length;
|
||||
load_parties_with_pagination(fromDate, toDate, newOffset, accumulated_data);
|
||||
load_customers_with_pagination(fromDate, toDate, newOffset, accumulated_data);
|
||||
} else {
|
||||
// Если больше нет инвойсов, переходим к обработке
|
||||
if (allInvoices.length > 0) {
|
||||
frappe.show_alert({
|
||||
message: __('Found {0} invoices. Processing parties...', [allInvoices.length]),
|
||||
message: __('Found {0} invoices. Processing customers...', [allInvoices.length]),
|
||||
indicator: 'green'
|
||||
}, 3);
|
||||
|
||||
// Теперь обрабатываем инвойсы батчами с прогресс баром
|
||||
setTimeout(() => {
|
||||
process_invoices_for_parties_with_progress(allInvoices, token);
|
||||
process_invoices_for_customers_with_progress(allInvoices, token);
|
||||
}, 1000);
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
|
|
@ -752,8 +752,8 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
|||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
check_and_process_token_parties(function() {
|
||||
show_invoice_filter_dialog();
|
||||
check_and_process_token_customers(function() {
|
||||
show_invoice_filter_dialog_customers();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
|
|
@ -767,13 +767,13 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
|||
frappe.msgprint({
|
||||
title: __('Error'),
|
||||
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) {
|
||||
// ИСПРАВЛЕНО: Очищаем состояние при ошибке
|
||||
cleanup_parties_loading_state();
|
||||
cleanup_customers_loading_state();
|
||||
frappe.msgprint({
|
||||
title: __('Network Error'),
|
||||
indicator: 'red',
|
||||
|
|
@ -783,33 +783,33 @@ function load_parties_with_pagination(fromDate, toDate, offset = 0, accumulated_
|
|||
});
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕННАЯ функция обработки инвойсов для контрагентов с прогресс баром
|
||||
function process_invoices_for_parties_with_progress(invoices, token, processedCount = 0, currentIndex = 0, batchSize = 50) {
|
||||
// ИСПРАВЛЕННАЯ функция обработки инвойсов для customers с прогресс баром
|
||||
function process_invoices_for_customers_with_progress(invoices, token, processedCount = 0, currentIndex = 0, batchSize = 50) {
|
||||
// Инициализация при первом вызове
|
||||
if (processedCount === 0 && currentIndex === 0) {
|
||||
window.cancelPartiesLoading = false;
|
||||
window.totalPartiesToProcess = invoices.length;
|
||||
window.cancelCustomersLoading = false;
|
||||
window.totalCustomersToProcess = invoices.length;
|
||||
|
||||
// Показываем прогресс бар
|
||||
frappe.show_progress(__('Processing Parties'), 0, invoices.length,
|
||||
__('Extracting parties from invoices...'), null, true);
|
||||
frappe.show_progress(__('Processing Customers'), 0, invoices.length,
|
||||
__('Extracting customers from invoices...'), null, true);
|
||||
|
||||
// Добавляем обработчик отмены
|
||||
$(document).off('progress-cancel.loading_parties');
|
||||
$(document).on('progress-cancel.loading_parties', function() {
|
||||
window.cancelPartiesLoading = true;
|
||||
$(document).off('progress-cancel.loading_customers');
|
||||
$(document).on('progress-cancel.loading_customers', function() {
|
||||
window.cancelCustomersLoading = true;
|
||||
frappe.show_alert({
|
||||
message: __('Cancelling party extraction...'),
|
||||
message: __('Cancelling customer extraction...'),
|
||||
indicator: 'orange'
|
||||
}, 3);
|
||||
});
|
||||
}
|
||||
|
||||
// Проверка отмены
|
||||
if (window.cancelPartiesLoading) {
|
||||
cleanup_parties_loading_state();
|
||||
if (window.cancelCustomersLoading) {
|
||||
cleanup_customers_loading_state();
|
||||
frappe.show_alert({
|
||||
message: __('Party extraction cancelled'),
|
||||
message: __('Customer extraction cancelled'),
|
||||
indicator: 'red'
|
||||
}, 3);
|
||||
return;
|
||||
|
|
@ -817,14 +817,14 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
|||
|
||||
// Если все инвойсы обработаны
|
||||
if (currentIndex >= invoices.length) {
|
||||
cleanup_parties_loading_state();
|
||||
cleanup_customers_loading_state();
|
||||
|
||||
// Показываем итоговый результат
|
||||
frappe.msgprint({
|
||||
title: __('Parties Loaded Successfully'),
|
||||
title: __('Customers Loaded Successfully'),
|
||||
indicator: 'green',
|
||||
message: __('Created {0} unique parties. Skipped {1} duplicates. Total invoices processed: {2}',
|
||||
[processedCount, 0, invoices.length]) // skipped считается на сервере
|
||||
message: __('Created {0} customers and {1} suppliers. Total invoices processed: {2}',
|
||||
[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);
|
||||
|
||||
// Обновляем прогресс
|
||||
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]));
|
||||
|
||||
// Обрабатываем текущий батч
|
||||
|
|
@ -855,29 +855,29 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
|||
callback: function(r) {
|
||||
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() {
|
||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
||||
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||
currentIndex + currentBatchSize, batchSize);
|
||||
}
|
||||
}, 100); // 100ms задержка между батчами
|
||||
|
||||
} else if (r.message && r.message.error === 'unauthorized') {
|
||||
cleanup_parties_loading_state();
|
||||
cleanup_customers_loading_state();
|
||||
|
||||
frappe.confirm(
|
||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||
function() {
|
||||
check_and_process_token_parties(function() {
|
||||
check_and_process_token_customers(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_parties_with_progress(invoices, login_r.message.main_token,
|
||||
process_invoices_for_customers_with_progress(invoices, login_r.message.main_token,
|
||||
processedCount, currentIndex, batchSize);
|
||||
} else {
|
||||
frappe.msgprint(__('Failed to get authentication token'));
|
||||
|
|
@ -896,8 +896,8 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
|||
} else {
|
||||
// Ошибка обработки батча - продолжаем со следующим
|
||||
setTimeout(function() {
|
||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
||||
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||
currentIndex + currentBatchSize, batchSize);
|
||||
}
|
||||
}, 200); // Увеличенная задержка при ошибках
|
||||
|
|
@ -906,8 +906,8 @@ function process_invoices_for_parties_with_progress(invoices, token, processedCo
|
|||
error: function(xhr, status, error) {
|
||||
// Сетевая ошибка - продолжаем со следующим батчом
|
||||
setTimeout(function() {
|
||||
if (!window.cancelPartiesLoading) { // Дополнительная проверка
|
||||
process_invoices_for_parties_with_progress(invoices, token, processedCount,
|
||||
if (!window.cancelCustomersLoading) { // Дополнительная проверка
|
||||
process_invoices_for_customers_with_progress(invoices, token, processedCount,
|
||||
currentIndex + currentBatchSize, batchSize);
|
||||
}
|
||||
}, 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,7 +1329,7 @@ SalesETaxes.import = {
|
|||
});
|
||||
},
|
||||
|
||||
// Обработка результата импорта
|
||||
// Обработка результата импорта - ОБНОВЛЕНО для Sales Order + Sales Invoice
|
||||
_handleImportResult: function(importR, invoiceMessage, invoiceId, scheduleDate, receiverName, total,
|
||||
invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
|
||||
if (importR.message && importR.message.success) {
|
||||
|
|
@ -1338,8 +1338,15 @@ SalesETaxes.import = {
|
|||
indicator: 'green'
|
||||
}, 3);
|
||||
|
||||
// ИЗМЕНЕНО: теперь получаем sales_order и sales_invoice из ответа
|
||||
const salesOrderName = importR.message.sales_order;
|
||||
const salesInvoiceName = importR.message.sales_invoice;
|
||||
|
||||
// ИЗМЕНЕНО: создание E-Taxes Sales и связывание с Sales Order происходит на сервере
|
||||
// Поэтому просто увеличиваем счетчик и продолжаем
|
||||
processedCount++;
|
||||
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)) {
|
||||
let errorType = '';
|
||||
const additionalData = { invoice_items: invoiceMessage.items || [] };
|
||||
|
|
@ -1490,9 +1497,8 @@ frappe.ui.form.on('Sales Order', {
|
|||
}
|
||||
});
|
||||
|
||||
// Sales Order List
|
||||
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) {
|
||||
if (doc.status === 'Closed') {
|
||||
|
|
@ -1511,6 +1517,7 @@ frappe.listview_settings['Sales Order'] = {
|
|||
},
|
||||
|
||||
onload: function(listview) {
|
||||
// ДОБАВЛЕНО: кнопка импорта из E-Taxes для Sales Order
|
||||
listview.page.add_menu_item(__('Import from E-Taxes'), function() {
|
||||
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'] = {
|
||||
add_fields: ['customer', 'posting_date', 'status', 'docstatus', 'is_taxes_doc'],
|
||||
|
||||
onload: function(listview) {
|
||||
// УБРАНО: кнопка импорта - теперь импорт только через Sales Order
|
||||
|
||||
listview.columns.push({
|
||||
type: 'Check',
|
||||
df: {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ doctype_js = {
|
|||
doctype_list_js = {
|
||||
"E-Taxes Item": "client/e_taxes_items_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",
|
||||
"Purchase Order": "client/purchase_order.js",
|
||||
"Purchase Invoice": "client/purchase_invoice.js",
|
||||
|
|
@ -28,6 +30,10 @@ doc_events = {
|
|||
"on_trash": "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": {
|
||||
"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'));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// =================================================================
|
||||
// КНОПКИ ДЛЯ 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() {
|
||||
if(frm.is_dirty()) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
"mappings_tab",
|
||||
"item_mappings_section",
|
||||
"item_mappings",
|
||||
"customer_mappings_section",
|
||||
"customer_mappings",
|
||||
"supplier_mappings_section",
|
||||
"supplier_mappings",
|
||||
"party_mappings_section",
|
||||
"party_mappings",
|
||||
"unit_mappings_section",
|
||||
|
|
@ -157,10 +161,32 @@
|
|||
"label": "Item Mappings",
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Business Partner Mappings"
|
||||
"label": "Business Partner Mappings (Legacy)"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_mappings",
|
||||
|
|
@ -182,7 +208,7 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-21 15:00:00.000000",
|
||||
"modified": "2025-07-11 14:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Invoice Az",
|
||||
"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()
|
||||
def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehouse=None):
|
||||
"""Import sales invoice taking into account mappings and processing unmapped elements"""
|
||||
def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, schedule_date=None, warehouse=None):
|
||||
"""Import sales invoice taking into account NEW customer mappings"""
|
||||
# Записываем активность
|
||||
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:
|
||||
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||
|
||||
party_mappings = {}
|
||||
for mapping in settings.party_mappings:
|
||||
if mapping.etaxes_party_name and mapping.erp_party:
|
||||
key = f"{mapping.etaxes_party_name.lower()}|{mapping.etaxes_tax_id.lower() if mapping.etaxes_tax_id else ''}"
|
||||
party_mappings[key] = (mapping.erp_party, mapping.party_type)
|
||||
# ИЗМЕНЕНО: Используем customer_mappings вместо party_mappings
|
||||
customer_mappings = {}
|
||||
for mapping in settings.customer_mappings:
|
||||
if mapping.etaxes_customer_name and mapping.erp_customer:
|
||||
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
|
||||
default_warehouse = warehouse
|
||||
|
||||
if not default_warehouse:
|
||||
# If warehouse not specified, try to get from settings
|
||||
if hasattr(settings, 'default_warehouse') and settings.default_warehouse:
|
||||
default_warehouse = settings.default_warehouse
|
||||
else:
|
||||
# If warehouse not specified in settings, try to get default warehouse for company
|
||||
company = frappe.defaults.get_user_default('Company')
|
||||
if company:
|
||||
default_warehouse = frappe.db.get_value('Company', company, 'default_warehouse')
|
||||
|
||||
# If still no warehouse, take first active warehouse
|
||||
if not default_warehouse:
|
||||
warehouses = frappe.get_all('Warehouse',
|
||||
filters={'is_group': 0, 'disabled': 0},
|
||||
|
|
@ -268,29 +266,24 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
if warehouses:
|
||||
default_warehouse = warehouses[0].name
|
||||
|
||||
# Check warehouse existence
|
||||
if not default_warehouse:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
||||
}
|
||||
|
||||
# Create new Sales Invoice
|
||||
si = frappe.new_doc('Sales Invoice')
|
||||
# ИЗМЕНЕНО: Создаем Sales Order сначала (аналогично Purchase Order логике)
|
||||
if sales_order_name:
|
||||
so = frappe.get_doc('Sales Order', sales_order_name)
|
||||
else:
|
||||
so = frappe.new_doc('Sales Order')
|
||||
|
||||
# Set customer
|
||||
# ИЗМЕНЕНО: Set customer используя customer_mappings
|
||||
receiver = invoice_data.get('receiver', {})
|
||||
receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}"
|
||||
|
||||
if receiver_key in party_mappings and party_mappings[receiver_key][0]:
|
||||
# Check if it's a customer
|
||||
if party_mappings[receiver_key][1] == 'Customer':
|
||||
si.customer = party_mappings[receiver_key][0]
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Receiver is mapped as {party_mappings[receiver_key][1]}, but Sales Invoice requires a Customer'
|
||||
}
|
||||
if receiver_key in customer_mappings and customer_mappings[receiver_key]:
|
||||
so.customer = customer_mappings[receiver_key]
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
|
|
@ -303,21 +296,24 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
}
|
||||
|
||||
# Set dates
|
||||
si.posting_date = frappe.utils.today()
|
||||
so.transaction_date = frappe.utils.today()
|
||||
|
||||
# Get date from invoice or parameter if specified
|
||||
if schedule_date:
|
||||
si.due_date = schedule_date
|
||||
so.delivery_date = schedule_date
|
||||
elif invoice_data.get("creationDate"):
|
||||
si.due_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
||||
so.delivery_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
||||
else:
|
||||
si.due_date = frappe.utils.add_days(frappe.utils.today(), 30)
|
||||
so.delivery_date = frappe.utils.today()
|
||||
|
||||
si.company = frappe.defaults.get_user_default('Company')
|
||||
so.company = frappe.defaults.get_user_default('Company')
|
||||
|
||||
# Add additional fields from invoice
|
||||
si.title = f"Sales Invoice {invoice_data.get('serialNumber', '')}"
|
||||
si.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
||||
# Add/update additional fields from invoice
|
||||
so.title = f"Sales Order {invoice_data.get('serialNumber', '')}"
|
||||
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
|
||||
if schedule_date:
|
||||
|
|
@ -360,7 +356,6 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
break
|
||||
|
||||
if not mapped_item:
|
||||
# Если соответствие не найдено, добавляем в список несопоставленных
|
||||
unmatched_items.append({
|
||||
'name': item_name,
|
||||
'code': item_code
|
||||
|
|
@ -372,7 +367,6 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
mapped_uom = None
|
||||
|
||||
if unit_name:
|
||||
# Ищем существующую запись E-Taxes Unit и берем mapped_unit
|
||||
try:
|
||||
mapped_uom = frappe.db.get_value('E-Taxes Unit',
|
||||
filters={'etaxes_unit_name': unit_name},
|
||||
|
|
@ -383,46 +377,41 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
# Если соответствие единицы не найдено, используем UOM из товара или настроек
|
||||
if not mapped_uom:
|
||||
if unit_name:
|
||||
# Добавляем в список несопоставленных единиц
|
||||
unmatched_units.append({
|
||||
'name': unit_name
|
||||
})
|
||||
|
||||
# Используем UOM из настроек товара или default
|
||||
try:
|
||||
item_doc = frappe.get_doc('Item', mapped_item)
|
||||
mapped_uom = item_doc.stock_uom
|
||||
except:
|
||||
# Fallback на default_uom из настроек
|
||||
mapped_uom = settings.default_uom if settings.default_uom else "Nos"
|
||||
|
||||
# Add position to Sales Invoice
|
||||
si_item = frappe.new_doc("Sales Invoice Item")
|
||||
si_item.parent = si.name
|
||||
si_item.parenttype = "Sales Invoice"
|
||||
si_item.parentfield = "items"
|
||||
# ИЗМЕНЕНО: Add position to Sales Order (не Sales Invoice)
|
||||
so_item = frappe.new_doc("Sales Order Item")
|
||||
so_item.parent = so.name
|
||||
so_item.parenttype = "Sales Order"
|
||||
so_item.parentfield = "items"
|
||||
|
||||
si_item.item_code = mapped_item
|
||||
so_item.item_code = mapped_item
|
||||
|
||||
# Получаем название и описание из базы данных Item
|
||||
try:
|
||||
item_doc = frappe.get_doc('Item', mapped_item)
|
||||
si_item.item_name = item_doc.item_name
|
||||
si_item.description = item_doc.description or item_doc.item_name
|
||||
so_item.item_name = item_doc.item_name
|
||||
so_item.description = item_doc.description or item_doc.item_name
|
||||
except:
|
||||
# Fallback на название из E-Taxes если не удалось получить из базы
|
||||
si_item.item_name = item.get("productName", "")
|
||||
si_item.description = item.get("productName", "")
|
||||
so_item.item_name = item.get("productName", "")
|
||||
so_item.description = item.get("productName", "")
|
||||
|
||||
si_item.qty = item.get("quantity", 0)
|
||||
si_item.rate = item.get("pricePerUnit", 0)
|
||||
si_item.amount = item.get("cost", 0)
|
||||
si_item.uom = mapped_uom
|
||||
so_item.qty = item.get("quantity", 0)
|
||||
so_item.rate = item.get("pricePerUnit", 0)
|
||||
so_item.amount = item.get("cost", 0)
|
||||
so_item.uom = mapped_uom
|
||||
so_item.delivery_date = date_to_use # ИЗМЕНЕНО: delivery_date вместо schedule_date
|
||||
so_item.warehouse = default_warehouse
|
||||
|
||||
# IMPORTANT: Set default warehouse
|
||||
si_item.warehouse = default_warehouse
|
||||
|
||||
si.append("items", si_item)
|
||||
so.append("items", so_item)
|
||||
added_items_count += 1
|
||||
|
||||
# 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
|
||||
if added_items_count == 0:
|
||||
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"
|
||||
)
|
||||
return {
|
||||
'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 в данных
|
||||
try:
|
||||
# Получаем VAT из E-Taxes данных
|
||||
vat_amount = invoice_data.get('vat', 0)
|
||||
|
||||
# Создаем строку ƏDV ТОЛЬКО если есть vat в данных и он больше 0
|
||||
if vat_amount and vat_amount > 0:
|
||||
vat_account = frappe.db.get_value('Account',
|
||||
filters={'account_number': '531.1'},
|
||||
filters={'account_number': '521.3'},
|
||||
fieldname='name')
|
||||
|
||||
if vat_account:
|
||||
# Рассчитываем net_total для определения ставки
|
||||
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
|
||||
|
||||
si.append("taxes", {
|
||||
so.append("taxes", {
|
||||
"account_head": vat_account,
|
||||
"charge_type": "On Net Total",
|
||||
"rate": vat_rate,
|
||||
|
|
@ -477,17 +463,27 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
|
|||
"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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
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'а
|
||||
invoice_id = invoice_data.get('id', '')
|
||||
|
|
@ -499,26 +495,49 @@ 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)
|
||||
if etaxes_sales_result and etaxes_sales_result.get('success'):
|
||||
# Устанавливаем поля E-Taxes ДО submit'а
|
||||
si.is_taxes_doc = 1
|
||||
si.taxes_doc = etaxes_sales_result.get('name')
|
||||
so.is_taxes_doc = 1
|
||||
so.taxes_doc = etaxes_sales_result.get('name')
|
||||
# Сохраняем изменения
|
||||
si.save()
|
||||
so.save()
|
||||
|
||||
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Invoice
|
||||
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Order
|
||||
try:
|
||||
si.submit()
|
||||
frappe.log_error(f"Sales Invoice {si.name} submitted successfully", "Import Sales Invoice Success")
|
||||
so.submit()
|
||||
frappe.log_error(f"Sales Order {so.name} submitted successfully", "Import Sales Invoice Success")
|
||||
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 {
|
||||
'success': False,
|
||||
'message': f'Failed to submit Sales Invoice: {str(e)}'
|
||||
'message': f'Failed to submit Sales Order: {str(e)}'
|
||||
}
|
||||
|
||||
# ИЗМЕНЕНИЕ: Создаем Sales Invoice из Sales Order
|
||||
try:
|
||||
si_name = create_sales_invoice_from_order(so.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_invoice': si.name
|
||||
'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:
|
||||
|
|
@ -683,3 +702,19 @@ def normalize_string(text, consider_azeri=True):
|
|||
text = ' '.join(text.split())
|
||||
|
||||
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