From 462b76d0b9142df070de8cfcb8fd04e03ca98998 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 13 May 2025 16:06:06 +0400 Subject: [PATCH] added logs and some error outputs --- invoice_az/api.py | 100 +- invoice_az/client/e_taxes_items_list.js | 643 ++++++++- invoice_az/client/e_taxes_parties_list.js | 637 ++++++++- invoice_az/client/purchase_order.js | 1263 +++++++++++------ .../doctype/asan_login/asan_login.js | 41 +- .../doctype/asan_login/asan_login.json | 12 +- test_invoice.git/HEAD | 1 + test_invoice.git/config | 8 + test_invoice.git/description | 1 + test_invoice.git/hooks/applypatch-msg.sample | 15 + test_invoice.git/hooks/commit-msg.sample | 24 + .../hooks/fsmonitor-watchman.sample | 174 +++ test_invoice.git/hooks/post-update.sample | 8 + test_invoice.git/hooks/pre-applypatch.sample | 14 + test_invoice.git/hooks/pre-commit.sample | 49 + .../hooks/pre-merge-commit.sample | 13 + test_invoice.git/hooks/pre-push.sample | 53 + test_invoice.git/hooks/pre-rebase.sample | 169 +++ test_invoice.git/hooks/pre-receive.sample | 24 + .../hooks/prepare-commit-msg.sample | 42 + .../hooks/push-to-checkout.sample | 78 + test_invoice.git/hooks/update.sample | 128 ++ test_invoice.git/info/exclude | 6 + ...a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.idx | Bin 0 -> 3900 bytes ...7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.pack | Bin 0 -> 86481 bytes test_invoice.git/packed-refs | 2 + 26 files changed, 2945 insertions(+), 560 deletions(-) create mode 100644 test_invoice.git/HEAD create mode 100644 test_invoice.git/config create mode 100644 test_invoice.git/description create mode 100755 test_invoice.git/hooks/applypatch-msg.sample create mode 100755 test_invoice.git/hooks/commit-msg.sample create mode 100755 test_invoice.git/hooks/fsmonitor-watchman.sample create mode 100755 test_invoice.git/hooks/post-update.sample create mode 100755 test_invoice.git/hooks/pre-applypatch.sample create mode 100755 test_invoice.git/hooks/pre-commit.sample create mode 100755 test_invoice.git/hooks/pre-merge-commit.sample create mode 100755 test_invoice.git/hooks/pre-push.sample create mode 100755 test_invoice.git/hooks/pre-rebase.sample create mode 100755 test_invoice.git/hooks/pre-receive.sample create mode 100755 test_invoice.git/hooks/prepare-commit-msg.sample create mode 100755 test_invoice.git/hooks/push-to-checkout.sample create mode 100755 test_invoice.git/hooks/update.sample create mode 100644 test_invoice.git/info/exclude create mode 100644 test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.idx create mode 100644 test_invoice.git/objects/pack/pack-02a7e7e33bd9a362208ec9a6cbd2b910ef8a15c5.pack create mode 100644 test_invoice.git/packed-refs 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 0000000000000000000000000000000000000000..34b0b6f1e325fadd54a94582e66294c8f6f55f92 GIT binary patch literal 3900 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 P^)cVfz0r=#dJg{s?4e}V literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..40ed613f96a19d155996f8f6a2549ec0c42ddf43 GIT binary patch literal 86481 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%0Dd=n_(i= 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

Q{u2853B2(!9B?*Tq|29HJ=Vl9h>;D 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 literal 0 HcmV?d00001 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