1144 lines
53 KiB
JavaScript
1144 lines
53 KiB
JavaScript
frappe.ui.form.on('Purchase Order', {
|
||
refresh: function(frm) {
|
||
if (frm.doc.docstatus === 0) {
|
||
frm.add_custom_button(__('Импорт из E-Taxes'), function() {
|
||
show_etaxes_import_dialog(frm);
|
||
}, __('Создать'));
|
||
}
|
||
}
|
||
});
|
||
|
||
frappe.listview_settings['Purchase Order'] = {
|
||
// Добавляем необходимые поля в запрос
|
||
add_fields: ['supplier', 'transaction_date', 'status', 'docstatus'],
|
||
|
||
// Настраиваем индикаторы статусов
|
||
get_indicator: function(doc) {
|
||
// Сохраняем стандартные индикаторы
|
||
if (doc.status === 'Closed') {
|
||
return [__('Closed'), 'green', 'status,=,Closed'];
|
||
} else if (doc.status === 'On Hold') {
|
||
return [__('On Hold'), 'orange', 'status,=,On Hold'];
|
||
} else if (doc.status === 'Delivered') {
|
||
return [__('Delivered'), 'green', 'status,=,Delivered'];
|
||
} else if (doc.docstatus == 1) {
|
||
return [__('To Receive and Bill'), 'orange', 'docstatus,=,1'];
|
||
} else if (doc.docstatus == 0) {
|
||
return [__('Draft'), 'red', 'docstatus,=,0'];
|
||
} else if (doc.docstatus == 2) {
|
||
return [__('Cancelled'), 'grey', 'docstatus,=,2'];
|
||
}
|
||
},
|
||
|
||
// Добавляем кнопку в верхнее меню списка
|
||
onload: function(listview) {
|
||
// Добавляем кнопку для импорта из E-Taxes
|
||
listview.page.add_menu_item(__('Импорт из E-Taxes'), function() {
|
||
show_etaxes_import_dialog_for_list();
|
||
});
|
||
}
|
||
};
|
||
|
||
// Глобальная переменная для хранения диалога загрузки
|
||
var loading_dialog = null;
|
||
|
||
// Функция для отображения диалога загрузки
|
||
function show_loading_dialog(message) {
|
||
if (!loading_dialog) {
|
||
loading_dialog = new frappe.ui.Dialog({
|
||
title: __('Загрузка'),
|
||
fields: [
|
||
{
|
||
fieldname: 'status_html',
|
||
fieldtype: 'HTML',
|
||
options: `
|
||
<div class="text-center">
|
||
<div class="lds-dual-ring" style="display: inline-block; width: 64px; height: 64px;">
|
||
<div style="width: 64px; height: 64px; border: 6px solid #4e73df; border-radius: 50%; border-color: #4e73df transparent #4e73df transparent; animation: lds-dual-ring 1.2s linear infinite;"></div>
|
||
</div>
|
||
<style>
|
||
@keyframes lds-dual-ring {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
<div class="mt-3" id="status_message">
|
||
<h4>${message || __('Пожалуйста, подождите...')}</h4>
|
||
<p>${__('Операция может занять некоторое время')}</p>
|
||
</div>
|
||
<!-- Блок для отображения кода верификации -->
|
||
<div id="verification_code_container" style="display:none; margin-top: 15px;">
|
||
<div style="background: #f8f9fa; padding: 10px; display: inline-block; border-radius: 5px; font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #333; border: 1px solid #ddd;">
|
||
<span id="verification_code">----</span>
|
||
</div>
|
||
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
],
|
||
primary_action_label: __('Отмена'),
|
||
primary_action: function() {
|
||
hide_loading_dialog();
|
||
}
|
||
});
|
||
} else {
|
||
// Обновляем сообщение
|
||
$('#status_message h4').text(message || __('Пожалуйста, подождите...'));
|
||
}
|
||
|
||
loading_dialog.show();
|
||
}
|
||
|
||
// Функция для скрытия диалога загрузки
|
||
function hide_loading_dialog() {
|
||
if (loading_dialog) {
|
||
loading_dialog.hide();
|
||
loading_dialog = null;
|
||
}
|
||
}
|
||
|
||
// Функция для обновления сообщения в диалоге загрузки
|
||
function update_loading_message(message, submessage) {
|
||
if (loading_dialog) {
|
||
$('#status_message h4').text(message);
|
||
if (submessage !== undefined) {
|
||
$('#status_message p').text(submessage);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Функция для отображения кода верификации
|
||
function show_verification_code(code) {
|
||
if (loading_dialog && code) {
|
||
$('#verification_code').text(code);
|
||
$('#verification_code_container').show();
|
||
$('#verification_code_container').attr('style', 'display: block !important; margin-top: 15px;');
|
||
}
|
||
}
|
||
|
||
// Функция для установки статуса успеха
|
||
function set_loading_success(message, submessage, callback, delay = 2000) {
|
||
if (loading_dialog) {
|
||
// Меняем анимацию на галочку
|
||
loading_dialog.$wrapper.find('.lds-dual-ring').html(`
|
||
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
|
||
</div>
|
||
`);
|
||
|
||
// Обновляем сообщения
|
||
$('#status_message h4').text(message);
|
||
if (submessage !== undefined) {
|
||
$('#status_message p').text(submessage);
|
||
}
|
||
|
||
// Скрываем код верификации
|
||
$('#verification_code_container').hide();
|
||
|
||
// Удаляем кнопки
|
||
loading_dialog.set_primary_action(null);
|
||
loading_dialog.set_secondary_action(null);
|
||
|
||
// Автоматически закрываем через указанную задержку
|
||
setTimeout(function() {
|
||
if (loading_dialog) {
|
||
loading_dialog.hide();
|
||
loading_dialog = null;
|
||
}
|
||
if (callback) callback();
|
||
}, delay);
|
||
} else if (callback) {
|
||
callback();
|
||
}
|
||
}
|
||
|
||
// Функция для установки статуса ошибки
|
||
function set_loading_error(message, submessage, show_close_button = true) {
|
||
if (loading_dialog) {
|
||
// Меняем анимацию на крестик
|
||
loading_dialog.$wrapper.find('.lds-dual-ring').html(`
|
||
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
|
||
<i class="fa fa-times" style="font-size: 48px; color: #d9534f;"></i>
|
||
</div>
|
||
`);
|
||
|
||
// Обновляем сообщения
|
||
$('#status_message h4').text(message);
|
||
if (submessage !== undefined) {
|
||
$('#status_message p').text(submessage);
|
||
}
|
||
|
||
// Скрываем код верификации
|
||
$('#verification_code_container').hide();
|
||
|
||
// При необходимости добавляем кнопку закрытия
|
||
if (show_close_button) {
|
||
loading_dialog.set_primary_action(__('Close'), function() {
|
||
hide_loading_dialog();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Функция для проверки токена и обработки аутентификации при необходимости
|
||
function check_token_before_api_call(callback) {
|
||
// Проверка токена без отображения загрузки
|
||
frappe.call({
|
||
method: 'invoice_az.api.check_token_validity',
|
||
callback: function(r) {
|
||
if (r.message && r.message.valid) {
|
||
// Токен действителен, продолжаем с callback-функцией сразу
|
||
callback();
|
||
} else {
|
||
// Токен недействителен, спрашиваем пользователя о ре-аутентификации
|
||
frappe.confirm(
|
||
__('Your E-Taxes session has expired or authentication is required. Would you like to authenticate now?'),
|
||
function() {
|
||
// Если пользователь согласен, запускаем процесс аутентификации
|
||
start_authentication_process(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(callback) {
|
||
show_loading_dialog(__('Starting Authentication'));
|
||
update_loading_message(__('Getting Asan Login settings...'), __('Please wait'));
|
||
|
||
frappe.call({
|
||
method: 'invoice_az.api.get_default_asan_login',
|
||
callback: function(r) {
|
||
if (r.message && r.message.found) {
|
||
var asan_login_name = r.message.name;
|
||
update_loading_message(
|
||
__('Sending authentication request...'),
|
||
__('This will send a request to your Asan Imza mobile app')
|
||
);
|
||
|
||
// Запускаем процесс аутентификации
|
||
frappe.call({
|
||
method: 'invoice_az.api.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) {
|
||
show_verification_code(r.message.verification_code);
|
||
}
|
||
|
||
// Изменяем сообщение для ожидания подтверждения
|
||
update_loading_message(
|
||
__('Waiting for confirmation on your phone'),
|
||
__('Please check your phone and confirm the authentication request')
|
||
);
|
||
|
||
// Опрашиваем статус аутентификации
|
||
poll_authentication_status(asan_login_name, bearer_token, function(success) {
|
||
if (success) {
|
||
// Если аутентификация успешна, продолжаем процесс
|
||
complete_authentication(asan_login_name, callback);
|
||
} else {
|
||
// Если аутентификация не удалась, показываем сообщение об ошибке
|
||
set_loading_error(
|
||
__('Authentication Failed'),
|
||
__('Could not authenticate with Asan Imza')
|
||
);
|
||
}
|
||
});
|
||
} else {
|
||
// Если запрос не удался, показываем сообщение об ошибке
|
||
set_loading_error(
|
||
__('Authentication Error'),
|
||
r.message ? r.message.message : __('Authentication request failed')
|
||
);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
// Если настройки не найдены, показываем сообщение об ошибке
|
||
set_loading_error(
|
||
__('No Asan Login Settings'),
|
||
__('Please configure Asan Login settings first')
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для опроса статуса аутентификации
|
||
function poll_authentication_status(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(
|
||
__('Authentication Timeout'),
|
||
__('The authentication request has timed out. Please try again.')
|
||
);
|
||
callback(false);
|
||
}
|
||
return;
|
||
}
|
||
|
||
attempts++;
|
||
|
||
// Обновляем счетчик попыток в сообщении
|
||
update_loading_message(
|
||
__('Waiting for confirmation on your phone'),
|
||
__('Please check your phone and confirm the authentication request') +
|
||
' (' + attempts + '/' + maxAttempts + ')'
|
||
);
|
||
|
||
// Проверяем статус авторизации
|
||
frappe.call({
|
||
method: 'invoice_az.api.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(
|
||
__('Authentication Successful'),
|
||
__('You are now authenticated with Asan Imza'),
|
||
function() {
|
||
callback(true);
|
||
}
|
||
);
|
||
} else {
|
||
// Продолжаем опрос
|
||
setTimeout(pollStatus, 6000);
|
||
}
|
||
} else {
|
||
// Если ошибка, останавливаем опрос и показываем сообщение
|
||
stopPolling = true;
|
||
set_loading_error(
|
||
__('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(asan_login_name, callback) {
|
||
show_loading_dialog(__('Completing Authentication'));
|
||
update_loading_message(__('Getting certificates...'), __('Please wait'));
|
||
|
||
// Получаем сертификаты
|
||
frappe.call({
|
||
method: 'invoice_az.api.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(
|
||
__('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(
|
||
__('Selecting certificate'),
|
||
cert_name
|
||
);
|
||
|
||
// Выбираем сертификат
|
||
frappe.call({
|
||
method: 'invoice_az.api.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(
|
||
__('Selecting taxpayer'),
|
||
__('Using certificate: ') + cert_name
|
||
);
|
||
|
||
// Выбираем налогоплательщика
|
||
frappe.call({
|
||
method: 'invoice_az.api.select_taxpayer',
|
||
args: {
|
||
'asan_login_name': asan_login_name
|
||
},
|
||
callback: function(tax_r) {
|
||
if (tax_r.message && tax_r.message.success) {
|
||
set_loading_success(
|
||
__('Authentication Complete'),
|
||
__('You can now access E-Taxes services'),
|
||
callback
|
||
);
|
||
} else {
|
||
hide_loading_dialog();
|
||
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
hide_loading_dialog();
|
||
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||
}
|
||
}
|
||
});
|
||
} catch (e) {
|
||
console.error('Error parsing certificate:', e);
|
||
hide_loading_dialog();
|
||
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||
}
|
||
} else {
|
||
hide_loading_dialog();
|
||
show_certificate_selector(cert_r.message.certificates, asan_login_name, callback);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
set_loading_error(
|
||
__('Error'),
|
||
cert_r.message ? cert_r.message.message : __('Failed to get certificates')
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для отображения селектора сертификатов
|
||
function show_certificate_selector(certificates, asan_login_name, callback) {
|
||
if (!certificates || !certificates.length) {
|
||
frappe.msgprint(__('No certificates available'));
|
||
return;
|
||
}
|
||
|
||
// Создаем таблицу сертификатов
|
||
var cert_html = '<div style="max-height: 400px; overflow-y: auto;"><table class="table table-bordered">';
|
||
cert_html += '<thead><tr><th>Type</th><th>Name</th><th>ID</th><th>Position</th><th>Status</th><th></th></tr></thead><tbody>';
|
||
|
||
certificates.forEach(function(cert, index) {
|
||
var name = '';
|
||
var id = '';
|
||
|
||
if (cert.taxpayerType === 'individual' && cert.individualInfo) {
|
||
name = cert.individualInfo.name || '';
|
||
id = cert.individualInfo.fin || '';
|
||
} else if (cert.legalInfo) {
|
||
name = cert.legalInfo.name || '';
|
||
id = cert.legalInfo.tin || cert.legalInfo.voen || '';
|
||
}
|
||
|
||
cert_html += '<tr>' +
|
||
'<td>' + (cert.taxpayerType || '') + '</td>' +
|
||
'<td>' + name + '</td>' +
|
||
'<td>' + id + '</td>' +
|
||
'<td>' + (cert.position || '') + '</td>' +
|
||
'<td>' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '</td>' +
|
||
'<td><button class="btn btn-xs btn-primary select-cert" data-index="' + index + '">Select</button></td>' +
|
||
'</tr>';
|
||
});
|
||
|
||
cert_html += '</tbody></table></div>';
|
||
|
||
var dialog = new frappe.ui.Dialog({
|
||
title: __('Select Certificate'),
|
||
fields: [{
|
||
fieldtype: 'HTML',
|
||
fieldname: 'certificates',
|
||
options: cert_html
|
||
}]
|
||
});
|
||
|
||
dialog.show();
|
||
|
||
// Обработчик нажатия на кнопку выбора сертификата
|
||
dialog.$wrapper.find('.select-cert').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(__('Selecting Certificate'));
|
||
update_loading_message(__('Processing your selection...'), certName);
|
||
|
||
// Отправляем запрос на выбор сертификата
|
||
frappe.call({
|
||
method: 'invoice_az.api.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(
|
||
__('Certificate selected'),
|
||
__('Now selecting taxpayer information')
|
||
);
|
||
|
||
// Выбираем налогоплательщика
|
||
frappe.call({
|
||
method: 'invoice_az.api.select_taxpayer',
|
||
args: {
|
||
'asan_login_name': asan_login_name
|
||
},
|
||
callback: function(r) {
|
||
if (r.message && r.message.success) {
|
||
set_loading_success(
|
||
__('Authentication Complete'),
|
||
__('You can now access E-Taxes services'),
|
||
callback
|
||
);
|
||
} else {
|
||
set_loading_error(
|
||
__('Error'),
|
||
r.message ? r.message.message : __('Failed to select taxpayer')
|
||
);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
set_loading_error(
|
||
__('Error'),
|
||
r.message ? r.message.message : __('Failed to select certificate')
|
||
);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Функция для отображения диалога импорта
|
||
function show_etaxes_import_dialog(frm) {
|
||
// Сначала проверяем токен
|
||
check_token_before_api_call(function() {
|
||
// Создаем диалог для выбора периода и склада
|
||
var d = new frappe.ui.Dialog({
|
||
title: __('Импорт счета-фактуры из E-Taxes'),
|
||
fields: [
|
||
{
|
||
fieldname: 'date_range_section',
|
||
fieldtype: 'Section Break',
|
||
label: __('Период')
|
||
},
|
||
{
|
||
fieldname: 'creationDateFrom',
|
||
fieldtype: 'Date',
|
||
label: __('Дата с'),
|
||
default: moment().subtract(1, 'month').format('YYYY-MM-DD')
|
||
},
|
||
{
|
||
fieldname: 'creationDateTo',
|
||
fieldtype: 'Date',
|
||
label: __('Дата по'),
|
||
default: moment().format('YYYY-MM-DD')
|
||
},
|
||
{
|
||
fieldname: 'settings_section',
|
||
fieldtype: 'Section Break',
|
||
label: __('Настройки')
|
||
},
|
||
{
|
||
fieldname: 'warehouse',
|
||
fieldtype: 'Link',
|
||
label: __('Склад по умолчанию'),
|
||
options: 'Warehouse',
|
||
reqd: true,
|
||
get_query: function() {
|
||
return {
|
||
filters: {
|
||
'is_group': 0,
|
||
'disabled': 0
|
||
}
|
||
};
|
||
}
|
||
}
|
||
],
|
||
primary_action_label: __('Поиск'),
|
||
primary_action: function() {
|
||
var values = d.get_values();
|
||
|
||
// Форматируем даты
|
||
let fromDate = values.creationDateFrom ? moment(values.creationDateFrom).format('DD-MM-YYYY 00:00') : '';
|
||
let toDate = values.creationDateTo ? moment(values.creationDateTo).format('DD-MM-YYYY 23:59') : '';
|
||
let warehouse = values.warehouse;
|
||
|
||
if (!warehouse) {
|
||
frappe.msgprint({
|
||
title: __('Внимание'),
|
||
indicator: 'orange',
|
||
message: __('Пожалуйста, выберите склад по умолчанию')
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Закрываем диалог
|
||
d.hide();
|
||
show_loading_dialog(__('Загрузка счетов-фактур...'));
|
||
|
||
// Загружаем счета-фактуры
|
||
load_etaxes_invoices(frm, fromDate, toDate, warehouse);
|
||
}
|
||
});
|
||
|
||
d.show();
|
||
});
|
||
}
|
||
|
||
// Новая функция для загрузки инвойсов с обработкой ошибок авторизации
|
||
function load_etaxes_invoices(frm, fromDate, toDate, warehouse) {
|
||
// Сначала получаем настройки авторизации
|
||
frappe.call({
|
||
method: 'invoice_az.api.get_default_asan_login',
|
||
callback: function(login_response) {
|
||
if (login_response.message && login_response.message.found) {
|
||
// Получаем токен из настроек
|
||
let main_token = login_response.message.main_token;
|
||
|
||
if (!main_token) {
|
||
hide_loading_dialog();
|
||
frappe.msgprint({
|
||
title: __('Ошибка авторизации'),
|
||
indicator: 'red',
|
||
message: __('Не найден токен авторизации. Пожалуйста, завершите процесс авторизации в настройках E-Taxes.')
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Загружаем счета-фактуры с использованием полученного токена
|
||
frappe.call({
|
||
method: 'invoice_az.api.get_invoices',
|
||
args: {
|
||
'token': main_token,
|
||
'filters': JSON.stringify({
|
||
"creationDateFrom": fromDate,
|
||
"creationDateTo": toDate
|
||
})
|
||
},
|
||
callback: function(r) {
|
||
// Закрываем диалог загрузки
|
||
hide_loading_dialog();
|
||
|
||
if (r.message && !r.message.error) {
|
||
// Получаем данные о счетах-фактурах
|
||
let invoices = r.message.data || r.message.invoices || [];
|
||
|
||
if (invoices.length > 0) {
|
||
// Передаем токен, frm и warehouse в следующий диалог
|
||
show_invoice_selection_dialog(frm, invoices, main_token, warehouse);
|
||
} else {
|
||
frappe.msgprint({
|
||
title: __('Информация'),
|
||
indicator: 'blue',
|
||
message: __('За указанный период не найдено счетов-фактур')
|
||
});
|
||
}
|
||
} else if (r.message && r.message.error === 'unauthorized') {
|
||
// Если ошибка авторизации, предлагаем авторизоваться
|
||
frappe.confirm(
|
||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||
function() {
|
||
// Запускаем процесс аутентификации и после успеха повторяем загрузку
|
||
start_authentication_process(function() {
|
||
// После успешной аутентификации повторяем загрузку
|
||
load_etaxes_invoices(frm, fromDate, toDate, warehouse);
|
||
});
|
||
},
|
||
function() {
|
||
// Если пользователь отказался, показываем сообщение
|
||
frappe.show_alert({
|
||
message: __('Authentication canceled. Operation cannot be completed.'),
|
||
indicator: 'red'
|
||
}, 5);
|
||
}
|
||
);
|
||
} else {
|
||
frappe.msgprint({
|
||
title: __('Ошибка'),
|
||
indicator: 'red',
|
||
message: r.message ? r.message.message : __('Ошибка загрузки счетов-фактур')
|
||
});
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
hide_loading_dialog();
|
||
frappe.msgprint({
|
||
title: __('Ошибка'),
|
||
indicator: 'red',
|
||
message: __('Не найдены настройки авторизации E-Taxes')
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для отображения диалога импорта при работе из списка
|
||
function show_etaxes_import_dialog_for_list() {
|
||
// Сначала проверяем токен
|
||
check_token_before_api_call(function() {
|
||
// Создаем диалог для выбора периода и склада
|
||
var d = new frappe.ui.Dialog({
|
||
title: __('Импорт счета-фактуры из E-Taxes'),
|
||
fields: [
|
||
{
|
||
fieldname: 'date_range_section',
|
||
fieldtype: 'Section Break',
|
||
label: __('Период')
|
||
},
|
||
{
|
||
fieldname: 'creationDateFrom',
|
||
fieldtype: 'Date',
|
||
label: __('Дата с'),
|
||
default: moment().subtract(1, 'month').format('YYYY-MM-DD')
|
||
},
|
||
{
|
||
fieldname: 'creationDateTo',
|
||
fieldtype: 'Date',
|
||
label: __('Дата по'),
|
||
default: moment().format('YYYY-MM-DD')
|
||
},
|
||
{
|
||
fieldname: 'settings_section',
|
||
fieldtype: 'Section Break',
|
||
label: __('Настройки')
|
||
},
|
||
{
|
||
fieldname: 'warehouse',
|
||
fieldtype: 'Link',
|
||
label: __('Склад по умолчанию'),
|
||
options: 'Warehouse',
|
||
reqd: true,
|
||
get_query: function() {
|
||
return {
|
||
filters: {
|
||
'is_group': 0,
|
||
'disabled': 0
|
||
}
|
||
};
|
||
}
|
||
}
|
||
],
|
||
primary_action_label: __('Поиск'),
|
||
primary_action: function() {
|
||
var values = d.get_values();
|
||
|
||
// Форматируем даты
|
||
let fromDate = values.creationDateFrom ? moment(values.creationDateFrom).format('DD-MM-YYYY 00:00') : '';
|
||
let toDate = values.creationDateTo ? moment(values.creationDateTo).format('DD-MM-YYYY 23:59') : '';
|
||
let warehouse = values.warehouse;
|
||
|
||
if (!warehouse) {
|
||
frappe.msgprint({
|
||
title: __('Внимание'),
|
||
indicator: 'orange',
|
||
message: __('Пожалуйста, выберите склад по умолчанию')
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Закрываем диалог
|
||
d.hide();
|
||
show_loading_dialog(__('Загрузка счетов-фактур...'));
|
||
|
||
// Загружаем счета-фактуры
|
||
load_etaxes_invoices(null, fromDate, toDate, warehouse);
|
||
}
|
||
});
|
||
|
||
d.show();
|
||
});
|
||
}
|
||
|
||
// Функция для отображения диалога с выбором счета-фактуры
|
||
function show_invoice_selection_dialog(frm, invoices, token, warehouse) {
|
||
// Создаем таблицу с инвойсами
|
||
var invoice_table = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-invoices-table">';
|
||
invoice_table += '<thead><tr>' +
|
||
'<th style="width: 30px;"><input type="checkbox" class="select-all-invoices"></th>' +
|
||
'<th>' + __('Номер') + '</th>' +
|
||
'<th>' + __('Дата') + '</th>' +
|
||
'<th>' + __('Поставщик') + '</th>' +
|
||
'<th>' + __('Сумма') + '</th>' +
|
||
'</tr></thead><tbody>';
|
||
|
||
invoices.forEach(function(invoice) {
|
||
let serialNumber = invoice.serialNumber || '';
|
||
let creationDate = invoice.creationDate ? moment(invoice.creationDate).format('DD.MM.YYYY') : '';
|
||
let senderName = invoice.sender ? invoice.sender.name : (invoice.senderName || '');
|
||
let amount = invoice.totalAmount || invoice.amount || 0;
|
||
|
||
invoice_table += '<tr>' +
|
||
'<td><input type="checkbox" class="select-invoice" data-id="' + invoice.id + '"></td>' +
|
||
'<td>' + serialNumber + '</td>' +
|
||
'<td>' + creationDate + '</td>' +
|
||
'<td>' + senderName + '</td>' +
|
||
'<td>' + format_currency(amount) + '</td>' +
|
||
'</tr>';
|
||
});
|
||
|
||
invoice_table += '</tbody></table></div>';
|
||
|
||
// Создаем диалог
|
||
var d = new frappe.ui.Dialog({
|
||
title: __('Выбор счета-фактуры'),
|
||
fields: [
|
||
{
|
||
fieldname: 'invoices_html',
|
||
fieldtype: 'HTML',
|
||
options: invoice_table
|
||
},
|
||
{
|
||
fieldname: 'warehouse_display',
|
||
fieldtype: 'HTML',
|
||
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
|
||
__('Выбранный склад: ') + '<strong>' + warehouse + '</strong></div></div></div>'
|
||
}
|
||
],
|
||
primary_action_label: __('Загрузить выбранные'),
|
||
primary_action: function() {
|
||
// Получаем выбранные инвойсы
|
||
var selected_invoice_ids = [];
|
||
d.$wrapper.find('.select-invoice:checked').each(function() {
|
||
selected_invoice_ids.push($(this).data('id'));
|
||
});
|
||
|
||
if (selected_invoice_ids.length === 0) {
|
||
frappe.msgprint({
|
||
title: __('Внимание'),
|
||
indicator: 'orange',
|
||
message: __('Не выбрано ни одного счета-фактуры')
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Закрываем диалог
|
||
d.hide();
|
||
|
||
// Загружаем выбранные инвойсы последовательно
|
||
load_selected_invoices(frm, selected_invoice_ids, token, warehouse);
|
||
},
|
||
secondary_action_label: __('Отмена'),
|
||
secondary_action: function() {
|
||
d.hide();
|
||
}
|
||
});
|
||
|
||
d.show();
|
||
|
||
// Добавляем обработчик для чекбокса "выбрать все"
|
||
d.$wrapper.find('.select-all-invoices').on('change', function() {
|
||
var is_checked = $(this).prop('checked');
|
||
d.$wrapper.find('.select-invoice').prop('checked', is_checked);
|
||
});
|
||
}
|
||
|
||
// Функция для загрузки выбранных инвойсов
|
||
function load_selected_invoices(frm, invoice_ids, token, warehouse) {
|
||
if (invoice_ids.length === 0) {
|
||
// Если все инвойсы обработаны, обновляем документ
|
||
if (frm) {
|
||
frm.reload_doc();
|
||
} else {
|
||
cur_list.refresh();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Берем первый ID из списка
|
||
var invoice_id = invoice_ids.shift();
|
||
|
||
// Показываем диалог загрузки
|
||
show_loading_dialog(__('Загрузка данных счета-фактуры (' + (invoice_ids.length + 1) + ' осталось)...'));
|
||
|
||
// Получаем детали счета-фактуры
|
||
frappe.call({
|
||
method: 'invoice_az.api.get_invoice_details',
|
||
args: {
|
||
'token': token,
|
||
'invoice_id': invoice_id
|
||
},
|
||
callback: function(r) {
|
||
// Закрываем диалог загрузки
|
||
hide_loading_dialog();
|
||
|
||
if (r.message && !r.message.error) {
|
||
// Получаем дату создания инвойса
|
||
let creationDate = r.message.creationDate || r.message.date || frappe.datetime.nowdate();
|
||
let scheduleDate = moment(creationDate).format('YYYY-MM-DD');
|
||
|
||
// Импортируем данные инвойса с указанием даты и склада
|
||
frappe.call({
|
||
method: 'invoice_az.api.import_invoice_with_mapping',
|
||
args: {
|
||
'invoice_data': r.message,
|
||
'purchase_order_name': frm ? frm.doc.name : null,
|
||
'schedule_date': scheduleDate, // Передаем дату в API
|
||
'warehouse': warehouse // Передаем склад в API
|
||
},
|
||
callback: function(import_r) {
|
||
if (import_r.message && import_r.message.success) {
|
||
// Показываем уведомление об успешном импорте
|
||
frappe.show_alert({
|
||
message: __('Инвойс успешно импортирован: ' + r.message.serialNumber),
|
||
indicator: 'green'
|
||
}, 3);
|
||
|
||
// Продолжаем с обработкой следующего инвойса
|
||
setTimeout(function() {
|
||
// Если создан новый заказ, обновляем ссылку на документ
|
||
if (import_r.message.purchase_order && frm && (import_r.message.purchase_order !== frm.doc.name)) {
|
||
frm = null;
|
||
}
|
||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||
}, 1000);
|
||
} else if (import_r.message && (import_r.message.unmatched_items || import_r.message.unmatched_parties)) {
|
||
// Если есть несопоставленные элементы или контрагенты
|
||
if (import_r.message.unmatched_items) {
|
||
show_unmatched_items_dialog(r.message, import_r.message.unmatched_items);
|
||
} else if (import_r.message.unmatched_parties) {
|
||
show_unmatched_parties_dialog(r.message, import_r.message.unmatched_parties);
|
||
}
|
||
|
||
// Останавливаем загрузку, так как требуется вмешательство пользователя
|
||
frappe.msgprint({
|
||
title: __('Импорт остановлен'),
|
||
indicator: 'red',
|
||
message: __('Для продолжения необходимо создать недостающие сопоставления')
|
||
});
|
||
} else {
|
||
frappe.msgprint({
|
||
title: __('Ошибка'),
|
||
indicator: 'red',
|
||
message: import_r.message ? import_r.message.message : __('Ошибка импорта данных счета-фактуры')
|
||
});
|
||
|
||
// Переходим к следующему инвойсу
|
||
setTimeout(function() {
|
||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||
}, 1000);
|
||
}
|
||
}
|
||
});
|
||
} else if (r.message && r.message.error === 'unauthorized') {
|
||
// Если ошибка авторизации, предлагаем авторизоваться заново
|
||
frappe.confirm(
|
||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||
function() {
|
||
// Запускаем процесс аутентификации и после успеха повторяем загрузку
|
||
start_authentication_process(function() {
|
||
// После успешной аутентификации получаем новый токен и повторяем загрузку
|
||
frappe.call({
|
||
method: 'invoice_az.api.get_default_asan_login',
|
||
callback: function(login_r) {
|
||
if (login_r.message && login_r.message.found && login_r.message.main_token) {
|
||
// Используем новый токен для загрузки
|
||
load_selected_invoices(frm, invoice_ids, login_r.message.main_token, warehouse);
|
||
} else {
|
||
frappe.msgprint({
|
||
title: __('Ошибка'),
|
||
indicator: 'red',
|
||
message: __('Не удалось получить токен после авторизации')
|
||
});
|
||
}
|
||
}
|
||
});
|
||
});
|
||
},
|
||
function() {
|
||
// Если пользователь отказался, показываем сообщение
|
||
frappe.show_alert({
|
||
message: __('Authentication canceled. Operation cannot be completed.'),
|
||
indicator: 'red'
|
||
}, 5);
|
||
}
|
||
);
|
||
} else {
|
||
frappe.msgprint({
|
||
title: __('Ошибка'),
|
||
indicator: 'red',
|
||
message: r.message ? r.message.message : __('Ошибка загрузки данных счета-фактуры')
|
||
});
|
||
|
||
// Переходим к следующему инвойсу
|
||
setTimeout(function() {
|
||
load_selected_invoices(frm, invoice_ids, token, warehouse);
|
||
}, 1000);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для отображения диалога с несопоставленными элементами
|
||
function show_unmatched_items_dialog(invoice_data, unmatched_items) {
|
||
// Создаем таблицу с несопоставленными товарами
|
||
var items_table = '<div class="text-center mb-3">' +
|
||
'<p>' + __('Для завершения импорта необходимо создать сопоставления для следующих товаров:') + '</p>' +
|
||
'</div>' +
|
||
'<div style="max-height: 300px; overflow-y: auto;"><table class="table table-bordered">';
|
||
items_table += '<thead><tr>' +
|
||
'<th>' + __('Название') + '</th>' +
|
||
'<th>' + __('Код') + '</th>' +
|
||
'</tr></thead><tbody>';
|
||
|
||
unmatched_items.forEach(function(item) {
|
||
items_table += '<tr>' +
|
||
'<td>' + item.name + '</td>' +
|
||
'<td>' + (item.code || '') + '</td>' +
|
||
'</tr>';
|
||
});
|
||
|
||
items_table += '</tbody></table></div>';
|
||
|
||
// Создаем диалог
|
||
var d = new frappe.ui.Dialog({
|
||
title: __('Несопоставленные товары'),
|
||
fields: [
|
||
{
|
||
fieldname: 'items_html',
|
||
fieldtype: 'HTML',
|
||
options: items_table
|
||
}
|
||
],
|
||
primary_action_label: __('Перейти к настройкам'),
|
||
primary_action: function() {
|
||
d.hide();
|
||
|
||
// Переходим к настройкам E-Taxes
|
||
frappe.set_route('Form', 'E-Taxes Settings');
|
||
},
|
||
secondary_action_label: __('Отмена'),
|
||
secondary_action: function() {
|
||
d.hide();
|
||
}
|
||
});
|
||
|
||
d.show();
|
||
}
|
||
|
||
// Функция для отображения диалога с несопоставленными контрагентами
|
||
function show_unmatched_parties_dialog(invoice_data, unmatched_parties) {
|
||
// Создаем таблицу с несопоставленными контрагентами
|
||
var parties_table = '<div class="text-center mb-3">' +
|
||
'<p>' + __('Для завершения импорта необходимо создать сопоставления для следующих контрагентов:') + '</p>' +
|
||
'</div>' +
|
||
'<div style="max-height: 300px; overflow-y: auto;"><table class="table table-bordered">';
|
||
parties_table += '<thead><tr>' +
|
||
'<th>' + __('Название') + '</th>' +
|
||
'<th>' + __('ИНН/VOEN') + '</th>' +
|
||
'<th>' + __('Тип') + '</th>' +
|
||
'</tr></thead><tbody>';
|
||
|
||
unmatched_parties.forEach(function(party) {
|
||
// Преобразуем тип контрагента для отображения
|
||
let partyTypeDisplay = '';
|
||
if (party.type === 'Sender') {
|
||
partyTypeDisplay = 'Поставщик (Sender)';
|
||
} else if (party.type === 'Receiver') {
|
||
partyTypeDisplay = 'Клиент (Receiver)';
|
||
} else {
|
||
partyTypeDisplay = party.type || '';
|
||
}
|
||
|
||
parties_table += '<tr>' +
|
||
'<td>' + party.name + '</td>' +
|
||
'<td>' + (party.tin || '') + '</td>' +
|
||
'<td>' + partyTypeDisplay + '</td>' +
|
||
'</tr>';
|
||
});
|
||
|
||
parties_table += '</tbody></table></div>';
|
||
|
||
// Создаем диалог
|
||
var d = new frappe.ui.Dialog({
|
||
title: __('Несопоставленные контрагенты'),
|
||
fields: [
|
||
{
|
||
fieldname: 'parties_html',
|
||
fieldtype: 'HTML',
|
||
options: parties_table
|
||
}
|
||
],
|
||
primary_action_label: __('Перейти к настройкам'),
|
||
primary_action: function() {
|
||
d.hide();
|
||
|
||
// Переходим к настройкам E-Taxes
|
||
frappe.set_route('Form', 'E-Taxes Settings');
|
||
},
|
||
secondary_action_label: __('Отмена'),
|
||
secondary_action: function() {
|
||
d.hide();
|
||
}
|
||
});
|
||
|
||
d.show();
|
||
}
|
||
|