invoice_az/invoice_az/client/purchase_order.js

1144 lines
53 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}