diff --git a/invoice_az/api.py b/invoice_az/api.py index e7b5f0b..b50759f 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -121,7 +121,14 @@ def get_invoices(token, filters=None): try: response = requests.post(url, data=json.dumps(payload), headers=headers) - + + if response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } + # Проверяем статус ответа для обработки 401 ошибки if response.status_code == 401: return { @@ -140,6 +147,12 @@ def get_invoices(token, filters=None): "error": "unauthorized", "message": "Authentication required. Please login again." } + elif e.response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } frappe.throw(f"Error getting invoices: {str(e)}") except Exception as e: @@ -162,6 +175,13 @@ def get_certificates(bearer_token): try: response = requests.get(url, headers=headers) + if response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } + # Проверяем на 401 ошибку if response.status_code == 401: return { @@ -180,6 +200,12 @@ def get_certificates(bearer_token): "error": "unauthorized", "message": "Authentication required. Please login again." } + elif e.response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } frappe.throw(f"Error getting certificates: {str(e)}") except Exception as e: @@ -212,6 +238,13 @@ def choose_taxpayer(bearer_token, owner_type, tin): try: response = requests.post(url, data=json.dumps(payload), headers=headers) + if response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } + # Проверяем на 401 ошибку if response.status_code == 401: return { @@ -239,6 +272,13 @@ def choose_taxpayer(bearer_token, owner_type, tin): "error": "unauthorized", "message": "Authentication required. Please login again." } + elif e.response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } + frappe.throw(f"Error choosing taxpayer: {str(e)}") except Exception as e: @@ -259,6 +299,13 @@ def get_invoice_details(token, invoice_id): try: response = requests.get(url, headers=headers) + if response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } + # Проверяем на 401 ошибку if response.status_code == 401: return { @@ -277,6 +324,12 @@ def get_invoice_details(token, invoice_id): "error": "unauthorized", "message": "Authentication required. Please login again." } + elif e.response.status_code == 500: + return { + "error": "server_error", + "message": "Service temporarily unavailable. Please try again in a few minutes.", + "status_code": 500 + } frappe.throw(f"Error getting invoice details: {str(e)}") except Exception as e: @@ -358,18 +411,25 @@ def handle_authentication(asan_login_name=None): asan_login.auth_status = "Waiting for confirmation" asan_login.save() + # Извлекаем код верификации из ответа, если он есть + verification_code = None + if auth_result.get("response_data") and isinstance(auth_result.get("response_data"), dict): + verification_code = auth_result.get("response_data").get("verificationCode", None) + return { "success": True, "message": "Authentication initiated. Please confirm on your phone.", "bearer_token": auth_result.get("bearer_token"), - "asan_login_name": asan_login_name + "asan_login_name": asan_login_name, + "verification_code": verification_code } else: return {"success": False, "message": "Failed to get authentication token."} except Exception as e: + frappe.log_error(f"Error in handle_authentication: {str(e)}\n{frappe.get_traceback()}", "Authentication Error") return {"success": False, "message": f"Error: {str(e)}"} - + @frappe.whitelist() def poll_auth_status(asan_login_name, bearer_token): """Проверяет статус аутентификации""" @@ -2491,3 +2551,37 @@ def handle_unauthorized_request(asan_login_name, operation_name="API request"): "operation": operation_name } +@frappe.whitelist() +def check_token_validity(asan_login_name=None): + """Проверяет актуальность токена для указанной настройки Asan Login""" + try: + # Если имя не указано, получаем настройки по умолчанию + if not asan_login_name: + default_settings = get_default_asan_login() + if not default_settings.get("found", False): + return {"valid": False, "message": "No Asan Login settings found."} + asan_login_name = default_settings.get("name") + + # Получаем документ Asan Login + asan_login = frappe.get_doc("Asan Login", asan_login_name) + + # Проверяем наличие main_token (это важно для E-Taxes) + if not asan_login.main_token: + return {"valid": False, "message": "Main token not found."} + + # Пытаемся получить сертификаты для проверки токена + certificates_result = get_certificates(asan_login.bearer_token) + + # Проверяем на ошибку авторизации + if certificates_result.get("error") == "unauthorized": + return { + "valid": False, + "message": "Authentication required. Token is expired." + } + + # Если нет ошибки, токен действителен + return {"valid": True, "message": "Token is valid."} + + except Exception as e: + frappe.log_error(f"Error in check_token_validity: {str(e)}\n{frappe.get_traceback()}", "Token Validity Check") + return {"valid": False, "message": f"Error: {str(e)}"} \ No newline at end of file diff --git a/invoice_az/client/e_taxes_items_list.js b/invoice_az/client/e_taxes_items_list.js index 3d14f0f..71e87b0 100644 --- a/invoice_az/client/e_taxes_items_list.js +++ b/invoice_az/client/e_taxes_items_list.js @@ -14,11 +14,567 @@ frappe.listview_settings['E-Taxes Item'] = { onload: function(listview) { // Add button to load items from E-Taxes listview.page.add_menu_item(__('Load from E-Taxes'), function() { - show_invoice_filter_dialog(); + // Сначала проверяем актуальность токена без анимации загрузки + check_and_process_token(function() { + show_invoice_filter_dialog(); + }); }); } }; +// Глобальная переменная для хранения диалога загрузки +var loading_dialog = null; + +// Функция для отображения диалога загрузки с анимацией +function show_loading_dialog(title, message, submessage) { + // Если диалог уже открыт, обновляем только сообщение + if (loading_dialog) { + $('#loading_title').text(title); + $('#loading_message').text(message); + if (submessage) { + $('#loading_submessage').text(submessage).show(); + } else { + $('#loading_submessage').hide(); + } + return; + } + + // Создаем диалог с анимацией загрузки + loading_dialog = new frappe.ui.Dialog({ + title: title, + fields: [ + { + fieldname: 'loading_html', + fieldtype: 'HTML', + options: ` +
+
+
+
+ +
+

${title}

+

${message}

+

${submessage || ''}

+ +
+
+ ` + } + ] + }); + + loading_dialog.show(); + loading_dialog.$wrapper.find('.modal-dialog').css('max-width', '450px'); +} + +// Функция для отображения кода верификации +function show_verification_code(code) { + if (loading_dialog && code) { + $('#verification_code').text(code); + $('#verification_code_container').show(); + } +} + +// Функция для скрытия диалога загрузки +function hide_loading_dialog() { + if (loading_dialog) { + loading_dialog.hide(); + loading_dialog = null; + } +} + +// Функция для обновления сообщения в диалоге загрузки +function update_loading_message(message, submessage) { + if (loading_dialog) { + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!submessage); + } + } +} + +// Функция для изменения статуса загрузки на успешный с автоматическим закрытием +function set_loading_success(message, submessage, callback, delay = 1500) { + if (loading_dialog) { + // Меняем анимацию на галочку + loading_dialog.$wrapper.find('.lds-dual-ring').html(` +
+ +
+ `); + + // Обновляем сообщения + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!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, вызываем его + callback(); + } +} + +// Функция для изменения статуса загрузки на ошибку +function set_loading_error(message, submessage, show_close_button = true) { + if (loading_dialog) { + // Меняем анимацию на крестик + loading_dialog.$wrapper.find('.lds-dual-ring').html(` +
+ +
+ `); + + // Обновляем сообщения + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!submessage); + } + + // Скрываем код верификации, если он был отображен + $('#verification_code_container').hide(); + + // При необходимости добавляем кнопку закрытия + if (show_close_button) { + loading_dialog.set_primary_action(__('Close'), function() { + hide_loading_dialog(); + }); + } + } +} + +// Функция для проверки токена и обработки аутентификации при необходимости +function check_and_process_token(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'), + __('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++; + + // Проверяем статус авторизации + 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 { + // Обновляем счетчик попыток в сообщении + update_loading_message( + __('Waiting for confirmation on your phone'), + __('Please check your phone and confirm the authentication request') + + ' (' + attempts + '/' + maxAttempts + ')' + ); + // Продолжаем опрос + 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'), + __('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, + 2000 // Автоматически закроется через 2 секунды + ); + } 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 = '
'; + cert_html += ''; + + 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_html += '
TypeNameIDPositionStatus
' + (cert.taxpayerType || '') + '' + name + '' + id + '' + (cert.position || '') + '' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '
'; + + 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'), + __('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, + 2000 // Автоматически закроется через 2 секунды + ); + } 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 to display the invoice filter dialog function show_invoice_filter_dialog() { // Get previous year for start date and current date for end date @@ -67,40 +623,17 @@ function show_invoice_filter_dialog() { // Close dialog d.hide(); - // Create a loading dialog with the spinner like in asan login - const loading_dialog = new frappe.ui.Dialog({ - title: __('Loading Items'), - fields: [ - { - fieldname: 'status_html', - fieldtype: 'HTML', - options: ` -
-
-
-
- -
-

Loading items from E-Taxes

-

Please wait, this process may take several minutes.

-
-
- ` - } - ] - }); - - loading_dialog.show(); - // Format dates correctly - convert from YYYY-MM-DD to DD-MM-YYYY HH:MM 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'); + // Показываем анимацию загрузки + show_loading_dialog( + __('Loading Items from E-Taxes'), + __('Retrieving invoices for the selected period...'), + `${fromDate} - ${toDate}` + ); + // Load items frappe.call({ method: 'invoice_az.api.load_items_from_invoices', @@ -110,24 +643,44 @@ function show_invoice_filter_dialog() { 'max_count': values.maxCount || 200 }, callback: function(r) { - // Close progress dialog - loading_dialog.hide(); - if (r.message && r.message.success) { - frappe.show_alert({ - message: __('Extracted and created {0} unique items. Skipped {1} duplicates.', + set_loading_success( + __('Items Loaded Successfully'), + __('Created {0} unique items. Skipped {1} duplicates.', [r.message.created_count, r.message.skipped_count || 0]), - indicator: 'green' - }, 5); + function() { + // Refresh list + cur_list.refresh(); + }, + 2000 // Автоматически закроется через 2 секунды + ); + } else if (r.message && r.message.unauthorized) { + // Если ошибка авторизации, запускаем процесс авторизации заново + set_loading_error( + __('Authentication Required'), + __('Your session has expired. Please authenticate again.'), + false // Не показываем кнопку закрытия + ); - // Refresh list - cur_list.refresh(); - } else { - frappe.msgprint({ - title: __('Error'), - indicator: 'red', - message: r.message ? r.message.message : __('An error occurred while loading items') + // Добавляем кнопку "Authenticate" + loading_dialog.set_primary_action(__('Authenticate'), function() { + hide_loading_dialog(); + + // Запускаем процесс авторизации и после него повторяем загрузку + check_and_process_token(function() { + show_invoice_filter_dialog(); + }); }); + + // Добавляем кнопку "Cancel" + loading_dialog.set_secondary_action(__('Cancel'), function() { + hide_loading_dialog(); + }); + } else { + set_loading_error( + __('Error'), + r.message ? r.message.message : __('An error occurred while loading items') + ); } } }); diff --git a/invoice_az/client/e_taxes_parties_list.js b/invoice_az/client/e_taxes_parties_list.js index 8bf4b45..0841cac 100644 --- a/invoice_az/client/e_taxes_parties_list.js +++ b/invoice_az/client/e_taxes_parties_list.js @@ -1,3 +1,5 @@ +// Добавляем механизм ре-аутентификации в список E-Taxes Parties + frappe.listview_settings['E-Taxes Parties'] = { add_fields: ['status', 'mapped_party'], @@ -14,11 +16,560 @@ frappe.listview_settings['E-Taxes Parties'] = { onload: function(listview) { // Add button to load parties from E-Taxes listview.page.add_menu_item(__('Load from E-Taxes'), function() { - show_invoice_filter_dialog(); + // Сначала проверяем актуальность токена перед загрузкой + check_and_process_token(function() { + show_invoice_filter_dialog(); + }); }); } }; +// Глобальная переменная для хранения диалога загрузки +var loading_dialog = null; + +// Функция для отображения диалога загрузки с анимацией +function show_loading_dialog(title, message, submessage) { + // Если диалог уже открыт, обновляем только сообщение + if (loading_dialog) { + $('#loading_title').text(title); + $('#loading_message').text(message); + if (submessage) { + $('#loading_submessage').text(submessage).show(); + } else { + $('#loading_submessage').hide(); + } + return; + } + + // Создаем диалог с анимацией загрузки + loading_dialog = new frappe.ui.Dialog({ + title: title, + fields: [ + { + fieldname: 'loading_html', + fieldtype: 'HTML', + options: ` +
+
+
+
+ +
+

${title}

+

${message}

+

${submessage || ''}

+ + +
+
+ ` + } + ] + }); + + loading_dialog.show(); + loading_dialog.$wrapper.find('.modal-dialog').css('max-width', '450px'); +} + +// Функция для скрытия диалога загрузки +function hide_loading_dialog() { + if (loading_dialog) { + loading_dialog.hide(); + loading_dialog = null; + } +} + +// Функция для обновления сообщения в диалоге загрузки +function update_loading_message(message, submessage) { + if (loading_dialog) { + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!submessage); + } + } +} + +// Функция для изменения статуса загрузки на успешный с автоматическим закрытием +function set_loading_success(message, submessage, callback, delay = 2000) { + if (loading_dialog) { + // Меняем анимацию на галочку + loading_dialog.$wrapper.find('.lds-dual-ring').html(` +
+ +
+ `); + + // Обновляем сообщения + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!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, вызываем его + callback(); + } +} + +// Функция для изменения статуса загрузки на ошибку +function set_loading_error(message, submessage, show_close_button = true) { + if (loading_dialog) { + // Меняем анимацию на крестик + loading_dialog.$wrapper.find('.lds-dual-ring').html(` +
+ +
+ `); + + // Обновляем сообщения + $('#loading_message').text(message); + if (submessage !== undefined) { + $('#loading_submessage').text(submessage); + $('#loading_submessage').toggle(!!submessage); + } + + // Скрываем код верификации при ошибке + $('#verification_code_container').hide(); + + // При необходимости добавляем кнопку закрытия + if (show_close_button) { + loading_dialog.set_primary_action(__('Close'), function() { + hide_loading_dialog(); + }); + } + } +} + +// Функция для проверки токена и обработки аутентификации при необходимости +function check_and_process_token(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'), + __('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) { + $('#verification_code').text(r.message.verification_code); + $('#verification_code_container').attr('style', 'display: block !important; margin-top: 15px;'); + } + + // Изменяем сообщение для ожидания подтверждения + 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'), + __('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 = '
'; + cert_html += ''; + + 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_html += '
TypeNameIDPositionStatus
' + (cert.taxpayerType || '') + '' + name + '' + id + '' + (cert.position || '') + '' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '
'; + + 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'), + __('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 to display the invoice filter dialog function show_invoice_filter_dialog() { // Get previous year for start date and current date for end date @@ -67,40 +618,17 @@ function show_invoice_filter_dialog() { // Close dialog d.hide(); - // Create a loading dialog with the spinner like in asan login - const loading_dialog = new frappe.ui.Dialog({ - title: __('Loading Parties'), - fields: [ - { - fieldname: 'status_html', - fieldtype: 'HTML', - options: ` -
-
-
-
- -
-

Loading parties from E-Taxes

-

Please wait, this process may take several minutes.

-
-
- ` - } - ] - }); - - loading_dialog.show(); - // Format dates correctly - convert from YYYY-MM-DD to DD-MM-YYYY HH:MM 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'); + // Показываем анимацию загрузки + show_loading_dialog( + __('Loading Parties from E-Taxes'), + __('Retrieving parties for the selected period...'), + `${fromDate} - ${toDate}` + ); + // Load parties frappe.call({ method: 'invoice_az.api.load_parties_from_invoices', @@ -110,24 +638,43 @@ function show_invoice_filter_dialog() { 'max_count': values.maxCount || 200 }, callback: function(r) { - // Close progress dialog - loading_dialog.hide(); - if (r.message && r.message.success) { - frappe.show_alert({ - message: __('Extracted and created {0} unique parties. Skipped {1} duplicates.', + set_loading_success( + __('Parties Loaded Successfully'), + __('Created {0} unique parties. Skipped {1} duplicates.', [r.message.created_count, r.message.skipped_count || 0]), - indicator: 'green' - }, 5); + function() { + // Refresh list + cur_list.refresh(); + } + ); + } else if (r.message && r.message.unauthorized) { + // Если ошибка авторизации, запускаем процесс авторизации заново + set_loading_error( + __('Authentication Required'), + __('Your session has expired. Please authenticate again.'), + false // Не показываем кнопку закрытия + ); - // Refresh list - cur_list.refresh(); - } else { - frappe.msgprint({ - title: __('Error'), - indicator: 'red', - message: r.message ? r.message.message : __('An error occurred while loading parties') + // Добавляем кнопку "Authenticate" + loading_dialog.set_primary_action(__('Authenticate'), function() { + hide_loading_dialog(); + + // Запускаем процесс авторизации и после него повторяем загрузку + check_and_process_token(function() { + show_invoice_filter_dialog(); + }); }); + + // Добавляем кнопку "Cancel" + loading_dialog.set_secondary_action(__('Cancel'), function() { + hide_loading_dialog(); + }); + } else { + set_loading_error( + __('Error'), + r.message ? r.message.message : __('An error occurred while loading parties') + ); } } }); diff --git a/invoice_az/client/purchase_order.js b/invoice_az/client/purchase_order.js index 6969895..ae2dde4 100644 --- a/invoice_az/client/purchase_order.js +++ b/invoice_az/client/purchase_order.js @@ -39,148 +39,781 @@ frappe.listview_settings['Purchase Order'] = { } }; -// Функция для отображения диалога импорта -function show_etaxes_import_dialog(frm) { - // Создаем диалог для выбора периода и склада - 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(__('Загрузка счетов-фактур...')); - - // Сначала получаем настройки авторизации - 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.msgprint({ - title: __('Ошибка авторизации'), - indicator: 'red', - message: __('Необходимо авторизоваться в системе E-Taxes. Токен устарел.') - }); - } else { - frappe.msgprint({ - title: __('Ошибка'), - indicator: 'red', - message: r.message ? r.message.message : __('Ошибка загрузки счетов-фактур') - }); +// Глобальная переменная для хранения диалога загрузки +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: ` +
+
+
+
+ +
+

${message || __('Пожалуйста, подождите...')}

+

${__('Операция может занять некоторое время')}

+
+ + +
+ ` } + ], + 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(` +
+ +
+ `); + + // Обновляем сообщения + $('#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(` +
+ +
+ `); + + // Обновляем сообщения + $('#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')); - d.show(); + 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 = '
'; + cert_html += ''; + + 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_html += '
TypeNameIDPositionStatus
' + (cert.taxpayerType || '') + '' + name + '' + id + '' + (cert.position || '') + '' + (cert.hasAccess ? 'Active' : 'Inactive') + (cert.liquidated ? ' (Liquidated)' : '') + '
'; + + 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(); + }); } // Функция для отображения диалога с выбором счета-фактуры @@ -354,13 +987,38 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) { } }); } else if (r.message && r.message.error === 'unauthorized') { - frappe.msgprint({ - title: __('Ошибка авторизации'), - indicator: 'red', - message: __('Необходимо авторизоваться в системе E-Taxes. Токен устарел.') - }); - - // Останавливаем загрузку из-за ошибки авторизации + // Если ошибка авторизации, предлагаем авторизоваться заново + 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: __('Ошибка'), @@ -377,50 +1035,6 @@ function load_selected_invoices(frm, invoice_ids, token, warehouse) { }); } -// Функция для импорта одного инвойса -function import_invoice_with_mappings(frm, invoice_data, warehouse) { - // Получаем дату создания инвойса - let creationDate = invoice_data.creationDate || invoice_data.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': invoice_data, - 'purchase_order_name': frm ? frm.doc.name : null, - 'schedule_date': scheduleDate, // Передаем дату в API - 'warehouse': warehouse // Передаем склад в API - }, - callback: function(r) { - if (r.message && r.message.success) { - frappe.show_alert({ - message: __('Данные счета-фактуры импортированы успешно'), - indicator: 'green' - }, 5); - - // Перезагружаем документ, если создан новый - if (r.message.purchase_order && (!frm || r.message.purchase_order !== frm.doc.name)) { - frappe.set_route('Form', 'Purchase Order', r.message.purchase_order); - } else if (frm) { - frm.reload_doc(); - } - } else if (r.message && r.message.unmatched_items) { - // Если есть несопоставленные элементы, показываем диалог с ними - show_unmatched_items_dialog(invoice_data, r.message.unmatched_items); - } else if (r.message && r.message.unmatched_parties) { - // Если есть несопоставленные контрагенты, показываем диалог с ними - show_unmatched_parties_dialog(invoice_data, r.message.unmatched_parties); - } else { - frappe.msgprint({ - title: __('Ошибка'), - indicator: 'red', - message: r.message ? r.message.message : __('Ошибка импорта данных счета-фактуры') - }); - } - } - }); -} - // Функция для отображения диалога с несопоставленными элементами function show_unmatched_items_dialog(invoice_data, unmatched_items) { // Создаем таблицу с несопоставленными товарами @@ -526,278 +1140,3 @@ function show_unmatched_parties_dialog(invoice_data, unmatched_parties) { d.show(); } - -// Функция для отображения диалога импорта при работе из списка -function show_etaxes_import_dialog_for_list() { - // Создаем диалог для выбора периода и склада - 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(__('Загрузка счетов-фактур...')); - - // Сначала получаем настройки авторизации - 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) { - // Передаем токен и warehouse в следующий диалог - show_invoice_selection_dialog_for_list(invoices, main_token, warehouse); - } else { - frappe.msgprint({ - title: __('Информация'), - indicator: 'blue', - message: __('За указанный период не найдено счетов-фактур') - }); - } - } else if (r.message && r.message.error === 'unauthorized') { - // Если ошибка авторизации, предлагаем авторизоваться - frappe.msgprint({ - title: __('Ошибка авторизации'), - indicator: 'red', - message: __('Необходимо авторизоваться в системе E-Taxes. Токен устарел.') - }); - } else { - frappe.msgprint({ - title: __('Ошибка'), - indicator: 'red', - message: r.message ? r.message.message : __('Ошибка загрузки счетов-фактур') - }); - } - } - }); - } else { - hide_loading_dialog(); - frappe.msgprint({ - title: __('Ошибка'), - indicator: 'red', - message: __('Не найдены настройки авторизации E-Taxes') - }); - } - } - }); - } - }); - - d.show(); -} - -// Функция для отображения диалога с выбором счета-фактуры при работе из списка -function show_invoice_selection_dialog_for_list(invoices, token, warehouse) { - // Создаем таблицу с инвойсами - var invoice_table = '
'; - invoice_table += '' + - '' + - '' + - '' + - '' + - '' + - ''; - - 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 += '' + - '' + - '' + - '' + - '' + - '' + - ''; - }); - - invoice_table += '
' + __('Номер') + '' + __('Дата') + '' + __('Поставщик') + '' + __('Сумма') + '
' + serialNumber + '' + creationDate + '' + senderName + '' + format_currency(amount) + '
'; - - // Создаем диалог - var d = new frappe.ui.Dialog({ - title: __('Выбор счета-фактуры'), - fields: [ - { - fieldname: 'invoices_html', - fieldtype: 'HTML', - options: invoice_table - }, - { - fieldname: 'warehouse_display', - fieldtype: 'HTML', - options: '
' + - __('Выбранный склад: ') + '' + warehouse + '
' - } - ], - 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(null, 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 show_loading_dialog(message) { - if (!window.loading_dialog) { - window.loading_dialog = new frappe.ui.Dialog({ - title: __('Загрузка'), - fields: [ - { - fieldname: 'status_html', - fieldtype: 'HTML', - options: ` -
-
-
-
- -
-

${message || __('Пожалуйста, подождите...')}

-

${__('Операция может занять некоторое время')}

-
-
- ` - } - ], - primary_action_label: __('Отмена'), - primary_action: function() { - window.loading_dialog.hide(); - } - }); - } else { - // Обновляем сообщение - $('#status_message h4').text(message || __('Пожалуйста, подождите...')); - } - - window.loading_dialog.show(); -} - -// Функция для скрытия диалога загрузки -function hide_loading_dialog() { - if (window.loading_dialog) { - window.loading_dialog.hide(); - } -} \ No newline at end of file diff --git a/invoice_az/invoice_az/doctype/asan_login/asan_login.js b/invoice_az/invoice_az/doctype/asan_login/asan_login.js index 72b7e2f..b123ef3 100644 --- a/invoice_az/invoice_az/doctype/asan_login/asan_login.js +++ b/invoice_az/invoice_az/doctype/asan_login/asan_login.js @@ -35,6 +35,21 @@ frappe.ui.form.on('Asan Login', { // Запускаем опрос статуса startPollingWithTimeout(frm); + + // Задержка, чтобы дать диалогу полностью создаться + setTimeout(function() { + // Если есть код верификации, отображаем его + if (r.message.verification_code) { + // Явно указываем текст кода + $('#verification_code').text(r.message.verification_code); + + // Отображаем контейнер кода верификации + $('#verification_code_container').show(); + + // Принудительно устанавливаем стиль отображения + $('#verification_code_container').attr('style', 'display: block !important; margin-top: 15px;'); + } + }, 500); // Задержка 500 мс для полной инициализации DOM } else { // Показываем сообщение об ошибке frappe.msgprint({ @@ -222,6 +237,13 @@ function startPollingWithTimeout(frm, successCallback) {

Waiting for confirmation on your phone

Please check your phone and confirm the authentication request.

+ + ` } @@ -257,6 +279,9 @@ function startPollingWithTimeout(frm, successCallback) { `); $('.lds-dual-ring').css('display', 'none'); + // Скрываем код верификации при таймауте + $('#verification_code_container').hide(); + // Обновляем поле auth_status и сохраняем frm.set_value('auth_status', 'Failed'); @@ -273,7 +298,6 @@ function startPollingWithTimeout(frm, successCallback) { } attempts++; - console.log(`Polling auth status, attempt ${attempts} of ${maxAttempts}`); // Проверяем статус авторизации frappe.call({ @@ -294,6 +318,13 @@ function startPollingWithTimeout(frm, successCallback) {

You are now authenticated. The page will reload shortly.

`); $('.lds-dual-ring').css('display', 'none'); + $('.lds-dual-ring').html(` +
+ +
+ `); + + // НЕ скрываем код верификации при успешной аутентификации // Обновляем кнопку status_dialog.set_primary_action(__('Continue'), function() { @@ -332,6 +363,14 @@ function startPollingWithTimeout(frm, successCallback) {

${r.message ? r.message.message : 'An unknown error occurred.'}

`); $('.lds-dual-ring').css('display', 'none'); + $('.lds-dual-ring').html(` +
+ +
+ `); + + // Скрываем код верификации при ошибке + $('#verification_code_container').hide(); // Обновляем поле auth_status frm.set_value('auth_status', 'Failed'); diff --git a/invoice_az/invoice_az/doctype/asan_login/asan_login.json b/invoice_az/invoice_az/doctype/asan_login/asan_login.json index b0a9aea..0ee0e3d 100644 --- a/invoice_az/invoice_az/doctype/asan_login/asan_login.json +++ b/invoice_az/invoice_az/doctype/asan_login/asan_login.json @@ -11,6 +11,7 @@ "user_id", "bearer_token", "auth_status", + "verification_code", "column_break_1", "is_default", "certificates_section", @@ -45,9 +46,7 @@ { "fieldname": "bearer_token", "fieldtype": "Small Text", - "hidden": 1, - "label": "Bearer Token", - "read_only": 1 + "label": "Bearer Token" }, { "default": "Not Authenticated", @@ -120,11 +119,16 @@ "label": "Taxpayer Response", "options": "JSON", "read_only": 1 + }, + { + "fieldname": "verification_code", + "fieldtype": "Data", + "label": "Verification Code" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-05-12 13:30:47.341250", + "modified": "2025-05-13 14:13:35.366465", "modified_by": "Administrator", "module": "Invoice Az", "name": "Asan Login", diff --git a/test_invoice.git/HEAD b/test_invoice.git/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/test_invoice.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/test_invoice.git/config b/test_invoice.git/config new file mode 100644 index 0000000..d0fa58e --- /dev/null +++ b/test_invoice.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true +[remote "origin"] + url = https://github.com/Awey01/test_invoice.git + fetch = +refs/*:refs/* + mirror = true diff --git a/test_invoice.git/description b/test_invoice.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/test_invoice.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test_invoice.git/hooks/applypatch-msg.sample b/test_invoice.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/test_invoice.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/test_invoice.git/hooks/commit-msg.sample b/test_invoice.git/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/test_invoice.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/test_invoice.git/hooks/fsmonitor-watchman.sample b/test_invoice.git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/test_invoice.git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/test_invoice.git/hooks/post-update.sample b/test_invoice.git/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/test_invoice.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/test_invoice.git/hooks/pre-applypatch.sample b/test_invoice.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/test_invoice.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/test_invoice.git/hooks/pre-commit.sample b/test_invoice.git/hooks/pre-commit.sample new file mode 100755 index 0000000..e144712 --- /dev/null +++ b/test_invoice.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/test_invoice.git/hooks/pre-merge-commit.sample b/test_invoice.git/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/test_invoice.git/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/test_invoice.git/hooks/pre-push.sample b/test_invoice.git/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/test_invoice.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/test_invoice.git/hooks/pre-rebase.sample b/test_invoice.git/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/test_invoice.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/test_invoice.git/hooks/pre-receive.sample b/test_invoice.git/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/test_invoice.git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/test_invoice.git/hooks/prepare-commit-msg.sample b/test_invoice.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/test_invoice.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/test_invoice.git/hooks/push-to-checkout.sample b/test_invoice.git/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/test_invoice.git/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/test_invoice.git/info/exclude b/test_invoice.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/test_invoice.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.idx b/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.idx new file mode 100644 index 0000000..34b0b6f Binary files /dev/null and b/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.idx differ diff --git a/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.pack b/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.pack new file mode 100644 index 0000000..40ed613 Binary files /dev/null and b/test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.pack differ diff --git a/test_invoice.git/packed-refs b/test_invoice.git/packed-refs new file mode 100644 index 0000000..aaba3b2 --- /dev/null +++ b/test_invoice.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +0a9ad34362a6013d554c45725822a7e9466b8ace refs/heads/main