+
- ----
+ ----
Verification Code
@@ -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(`
`);
// Обновляем сообщения
- $('#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(`
`);
// Обновляем сообщения
- $('#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
'
' + id + ' | ' +
'
' + (cert.position || '') + ' | ' +
'
' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + ' | ' +
- '
| ' +
+ '
| ' +
'';
});
@@ -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); // Еще большая задержка при сетевых ошибках
diff --git a/invoice_az/client/e_taxes_suppliers_list.js b/invoice_az/client/e_taxes_suppliers_list.js
new file mode 100644
index 0000000..fee5b52
--- /dev/null
+++ b/invoice_az/client/e_taxes_suppliers_list.js
@@ -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: `
+
+
+
+
+
${title}
+
${message}
+
${submessage || ''}
+
+
+
+ ----
+
+
Verification Code
+
+
+
+ `
+ }
+ ]
+ });
+
+ 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(`
+
+
+
+ `);
+
+ // Обновляем сообщения
+ $('#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(`
+
+
+
+ `);
+
+ // Обновляем сообщения
+ $('#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 = '
';
+ cert_html += '| Type | Name | ID | Position | Status | |
';
+
+ 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 += '' +
+ '| ' + (cert.taxpayerType || '') + ' | ' +
+ '' + name + ' | ' +
+ '' + id + ' | ' +
+ '' + (cert.position || '') + ' | ' +
+ '' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + ' | ' +
+ ' | ' +
+ '
';
+ });
+
+ cert_html += '
';
+
+ 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); // Еще большая задержка при сетевых ошибках
+ }
+ });
+}
\ No newline at end of file
diff --git a/invoice_az/client/sales_order.js b/invoice_az/client/sales_order.js
index 55079f2..4538790 100644
--- a/invoice_az/client/sales_order.js
+++ b/invoice_az/client/sales_order.js
@@ -1329,17 +1329,24 @@ SalesETaxes.import = {
});
},
- // Обработка результата импорта
+ // Обработка результата импорта - ОБНОВЛЕНО для Sales Order + Sales Invoice
_handleImportResult: function(importR, invoiceMessage, invoiceId, scheduleDate, receiverName, total,
- invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
+ invoiceIds, token, warehouse, processedCount, currentIndex, isLastInvoice) {
if (importR.message && importR.message.success) {
frappe.show_alert({
message: __('Sales invoice successfully imported: ') + (invoiceMessage.serialNumber || ''),
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: {
diff --git a/invoice_az/hooks.py b/invoice_az/hooks.py
index 27eedac..148b2c8 100644
--- a/invoice_az/hooks.py
+++ b/invoice_az/hooks.py
@@ -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"
}
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/__init__.py b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json
new file mode 100644
index 0000000..b45573a
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.json
@@ -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
+}
\ No newline at end of file
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.py b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.py
new file mode 100644
index 0000000..e0fa7af
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customer_mappings/e_taxes_customer_mappings.py
@@ -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
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customers/__init__.py b/invoice_az/invoice_az/doctype/e_taxes_customers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.js b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.js
new file mode 100644
index 0000000..77373fd
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.js
@@ -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) {
+
+// },
+// });
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.json b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.json
new file mode 100644
index 0000000..4c4cac2
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.json
@@ -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
+}
\ No newline at end of file
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.py b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.py
new file mode 100644
index 0000000..dced5b3
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customers/e_taxes_customers.py
@@ -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
diff --git a/invoice_az/invoice_az/doctype/e_taxes_customers/test_e_taxes_customers.py b/invoice_az/invoice_az/doctype/e_taxes_customers/test_e_taxes_customers.py
new file mode 100644
index 0000000..7602264
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_customers/test_e_taxes_customers.py
@@ -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
diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js
index c4e2295..7c6a17d 100644
--- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js
+++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js
@@ -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(
+ __('
WARNING! This action will create new customers in the system for all unmapped elements!
This action is irreversible. Are you sure?
'),
+ 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(
+ __('
WARNING! This action will create new suppliers in the system for all unmapped elements!
This action is irreversible. Are you sure?
'),
+ 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()) {
diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json
index 17d7dc0..2d0e9fd 100644
--- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json
+++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json
@@ -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",
diff --git a/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/__init__.py b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json
new file mode 100644
index 0000000..082e281
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.json
@@ -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
+}
\ No newline at end of file
diff --git a/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.py b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.py
new file mode 100644
index 0000000..b156274
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_supplier_mappings/e_taxes_supplier_mappings.py
@@ -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
diff --git a/invoice_az/invoice_az/doctype/e_taxes_suppliers/__init__.py b/invoice_az/invoice_az/doctype/e_taxes_suppliers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.js b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.js
new file mode 100644
index 0000000..ecc2307
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.js
@@ -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) {
+
+// },
+// });
diff --git a/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.json b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.json
new file mode 100644
index 0000000..cbf4830
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.json
@@ -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
+}
\ No newline at end of file
diff --git a/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.py b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.py
new file mode 100644
index 0000000..57fc9c0
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_suppliers/e_taxes_suppliers.py
@@ -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
diff --git a/invoice_az/invoice_az/doctype/e_taxes_suppliers/test_e_taxes_suppliers.py b/invoice_az/invoice_az/doctype/e_taxes_suppliers/test_e_taxes_suppliers.py
new file mode 100644
index 0000000..ac3791b
--- /dev/null
+++ b/invoice_az/invoice_az/doctype/e_taxes_suppliers/test_e_taxes_suppliers.py
@@ -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
diff --git a/invoice_az/sales_api.py b/invoice_az/sales_api.py
index 37eeebe..9cf28dc 100644
--- a/invoice_az/sales_api.py
+++ b/invoice_az/sales_api.py
@@ -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,56 +266,54 @@ 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')
-
- # Set customer
- 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]
+ # ИЗМЕНЕНО: Создаем 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 используя customer_mappings
+ receiver = invoice_data.get('receiver', {})
+ receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}"
+
+ if receiver_key in customer_mappings and customer_mappings[receiver_key]:
+ so.customer = customer_mappings[receiver_key]
else:
return {
'success': False,
- 'message': f'Receiver is mapped as {party_mappings[receiver_key][1]}, but Sales Invoice requires a Customer'
+ 'unmatched_parties': [{
+ 'name': receiver.get('name', ''),
+ 'tin': receiver.get('tin', ''),
+ 'type': 'Receiver'
+ }],
+ 'message': f'No mapping found for customer: {receiver.get("name", "")}'
}
- else:
- return {
- 'success': False,
- 'unmatched_parties': [{
- 'name': receiver.get('name', ''),
- 'tin': receiver.get('tin', ''),
- 'type': 'Receiver'
- }],
- 'message': f'No mapping found for customer: {receiver.get("name", "")}'
- }
-
- # Set dates
- si.posting_date = frappe.utils.today()
-
- # Get date from invoice or parameter if specified
- if schedule_date:
- si.due_date = schedule_date
- elif invoice_data.get("creationDate"):
- si.due_date = frappe.utils.getdate(invoice_data.get("creationDate"))
- else:
- si.due_date = frappe.utils.add_days(frappe.utils.today(), 30)
- si.company = frappe.defaults.get_user_default('Company')
+ # Set dates
+ so.transaction_date = frappe.utils.today()
+
+ if schedule_date:
+ so.delivery_date = schedule_date
+ elif invoice_data.get("creationDate"):
+ so.delivery_date = frappe.utils.getdate(invoice_data.get("creationDate"))
+ else:
+ so.delivery_date = frappe.utils.today()
+
+ so.company = frappe.defaults.get_user_default('Company')
- # Add additional fields from invoice
- 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
-
- # IMPORTANT: Set default warehouse
- si_item.warehouse = default_warehouse
+ 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
- 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,27 +495,50 @@ def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehous
etaxes_sales_result = create_etaxes_sales(invoice_id, date_to_use, receiver_name, total)
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)}'
}
- return {
- 'success': True,
- 'message': 'Sales invoice data imported successfully.',
- 'sales_invoice': si.name
- }
+ # ИЗМЕНЕНИЕ: Создаем 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 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:
frappe.log_error(f"Error in import_sales_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Sales Invoice Error")
@@ -683,3 +702,19 @@ def normalize_string(text, consider_azeri=True):
text = ' '.join(text.split())
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")