From 19f82f7f8e8ca5f43ceda38515a7a39fa1162a06 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Thu, 11 Jun 2026 11:37:14 +0000 Subject: [PATCH] refactor(company): drop dead E-Taxes profile/auth code from company.js Remove the now-unreachable profile-fetch and Asan inline-auth machinery (checkAuthAndFetchProfile, showProfileDialog, startInlineAuthentication, poll/complete/select cert, profile parsing/formatting helpers and the dictionary cache) left orphaned after the E-Taxes buttons were removed. Keeps gambling-activity handling, declaration defaults and tax wizards. 1797 -> 313 lines. Co-Authored-By: Claude Opus 4.8 (1M context) --- taxes_az/client/company.js | 1508 +----------------------------------- 1 file changed, 12 insertions(+), 1496 deletions(-) diff --git a/taxes_az/client/company.js b/taxes_az/client/company.js index 442b21d..fcd59d3 100644 --- a/taxes_az/client/company.js +++ b/taxes_az/client/company.js @@ -1,6 +1,6 @@ /** * Company DocType Enhancement for E-Taxes Integration -* Adds functionality to fetch company profile data from E-Taxes +* Adds gambling-activity handling, declaration defaults and tax wizard links */ frappe.ui.form.on('Company', { @@ -47,15 +47,6 @@ frappe.ui.form.on('Company', { // ======= ETAXES COMPANY MODULE ======= window.ETaxesCompany = { - // Хранение ссылки на текущий загрузочный диалог - currentLoadingDialog: null, - - // Кэш для справочников - _dictionaryCache: { - taxAuthorities: null, - propertyTypes: null - }, - // Gambling activity codes gamblingActivityCodes: ['92000', '9200003', '9200004', '9200001', '9200002', '9200005'], @@ -65,7 +56,7 @@ window.ETaxesCompany = { checkGamblingActivity: function(frm) { const mainActivity = frm.doc.main_activity; const isGamblingActivity = ETaxesCompany.gamblingActivityCodes.includes(mainActivity); - + if (isGamblingActivity) { let needsUpdate = false; if (!frm.doc.seller && !frm.doc.organizer) { @@ -73,31 +64,31 @@ window.ETaxesCompany = { frm.doc.organizer = 1; needsUpdate = true; } - + frm.set_df_property('seller', 'read_only', 0); frm.set_df_property('organizer', 'read_only', 0); frm.set_df_property('seller', 'hidden', 0); frm.set_df_property('organizer', 'hidden', 0); frm.refresh_field('seller'); frm.refresh_field('organizer'); - + if (needsUpdate) { frappe.show_alert(__('Gambling activity detected. Seller and Organizer flags enabled.'), 'blue'); } } else { frm.set_df_property('seller', 'read_only', 1); frm.set_df_property('organizer', 'read_only', 1); - + let needsUpdate = false; if (frm.doc.seller || frm.doc.organizer) { frm.doc.seller = 0; frm.doc.organizer = 0; needsUpdate = true; } - + frm.refresh_field('seller'); frm.refresh_field('organizer'); - + if (needsUpdate) { frappe.show_alert(__('Non-gambling activity. Seller and Organizer flags disabled.'), 'blue'); } @@ -110,21 +101,21 @@ window.ETaxesCompany = { handleGamblingFieldChange: function(frm, fieldName) { const mainActivity = frm.doc.main_activity; const isGamblingActivity = ETaxesCompany.gamblingActivityCodes.includes(mainActivity); - + if (!isGamblingActivity) { frm.doc[fieldName] = 0; frm.refresh_field(fieldName); frappe.msgprint(__('Seller and Organizer flags can only be set for gambling activities.')); return; } - + if (!frm.doc.seller && !frm.doc.organizer) { frm.doc[fieldName] = 1; frm.refresh_field(fieldName); frappe.msgprint(__('At least one of Seller or Organizer must be checked for gambling activities.')); return; } - + ETaxesCompany.performForcedSave(frm, { [fieldName]: frm.doc[fieldName] ? 1 : 0 }); @@ -161,7 +152,7 @@ window.ETaxesCompany = { validateMainActivity: function(frm) { const activityCode = frm.doc.main_activity; if (!activityCode) return; - + frappe.call({ method: 'frappe.client.get_value', args: { @@ -213,1481 +204,6 @@ window.ETaxesCompany = { } } }); - }, - - /** - * Check authentication and fetch profile with inline re-authentication - */ - checkAuthAndFetchProfile: function(frm) { - // Check if ETaxes common module is available - if (typeof ETaxes !== 'undefined' && ETaxes.auth) { - // Use ETaxes common authentication system - ETaxes.auth.checkAndProcess(function() { - ETaxesCompany.fetchProfile(frm); - }); - } else { - // Fallback to simple token validity check - frappe.call({ - method: 'taxes_az.auth.check_token_validity', - callback: function(r) { - if (r.message && r.message.valid) { - ETaxesCompany.fetchProfile(frm); - } else { - ETaxesCompany.handleAuthenticationRequired(frm); - } - }, - error: function() { - frappe.msgprint(__('Error checking authentication status')); - } - }); - } - }, - - /** - * Handle authentication required with inline process - */ - handleAuthenticationRequired: function(frm) { - frappe.confirm( - __('E-Taxes authentication is required. Would you like to authenticate now?'), - function() { - ETaxesCompany.startInlineAuthentication(frm); - }, - function() { - frappe.show_alert({ - message: __('Authentication cancelled'), - indicator: 'red' - }, 3); - } - ); - }, - - /** - * Start inline authentication process - */ - startInlineAuthentication: function(frm) { - // Show loading dialog - ETaxesCompany.currentLoadingDialog = new frappe.ui.Dialog({ - title: __('E-Taxes Authentication'), - indicator: 'blue', - fields: [ - { - fieldname: 'loading_html', - fieldtype: 'HTML', - options: ETaxesCompany._getAuthLoadingHTML(__('Starting Authentication'), __('Getting Asan Login settings...')) - } - ], - primary_action_label: __('Cancel'), - primary_action: function() { - ETaxesCompany._hideLoadingDialog(); - } - }); - - ETaxesCompany.currentLoadingDialog.show(); - - // Get default login settings - frappe.call({ - method: 'taxes_az.auth.get_default_asan_login', - callback: function(r) { - if (r.message && r.message.found) { - const asanLoginName = r.message.name; - - // Update loading message - ETaxesCompany._updateLoadingDialog(ETaxesCompany.currentLoadingDialog, - __('Sending authentication request...'), - __('This will send a request to your Asan Imza mobile app')); - - // Start authentication - frappe.call({ - method: 'taxes_az.auth.handle_authentication', - args: { 'asan_login_name': asanLoginName }, - callback: function(authR) { - if (authR.message && authR.message.success) { - const bearerToken = authR.message.bearer_token; - - // Show verification code if available - if (authR.message.verification_code) { - ETaxesCompany._showVerificationCode(ETaxesCompany.currentLoadingDialog, authR.message.verification_code); - } - - ETaxesCompany._updateLoadingDialog(ETaxesCompany.currentLoadingDialog, - __('Waiting for confirmation on your phone'), - __('Please check your phone and confirm the authentication request')); - - // Start polling - ETaxesCompany.pollAuthStatus(ETaxesCompany.currentLoadingDialog, asanLoginName, bearerToken, function(success) { - if (success) { - ETaxesCompany.completeAuthentication(ETaxesCompany.currentLoadingDialog, asanLoginName, function() { - ETaxesCompany._hideLoadingDialog(); - ETaxesCompany.fetchProfile(frm); - }); - } else { - ETaxesCompany._setDialogError(ETaxesCompany.currentLoadingDialog, - __('Authentication Failed'), - __('Could not authenticate with Asan Imza')); - } - }); - } else { - ETaxesCompany._setDialogError(ETaxesCompany.currentLoadingDialog, - __('Authentication Error'), - authR.message ? authR.message.message : __('Authentication request failed')); - } - }, - error: function() { - ETaxesCompany._setDialogError(ETaxesCompany.currentLoadingDialog, - __('Network Error'), - __('Failed to start authentication process')); - } - }); - } else { - ETaxesCompany._setDialogError(ETaxesCompany.currentLoadingDialog, - __('No Asan Login Settings'), - __('Please configure Asan Login settings first')); - } - }, - error: function() { - ETaxesCompany._setDialogError(ETaxesCompany.currentLoadingDialog, - __('Error'), - __('Failed to get authentication settings')); - } - }); - }, - - /** - * Poll authentication status - */ - pollAuthStatus: function(dialog, asanLoginName, bearerToken, callback) { - let attempts = 0; - const maxAttempts = 20; - const pollInterval = 6000; // 6 seconds - - function pollStep() { - if (attempts >= maxAttempts) { - ETaxesCompany._setDialogError(dialog, - __('Authentication Timeout'), - __('The authentication request has timed out. Please try again.')); - callback(false); - return; - } - - attempts++; - - ETaxesCompany._updateLoadingDialog(dialog, - __('Waiting for confirmation on your phone'), - __('Please check your phone and confirm the authentication request') + - ' (' + attempts + '/' + maxAttempts + ')'); - - frappe.call({ - method: 'taxes_az.auth.poll_auth_status', - args: { - 'asan_login_name': asanLoginName, - 'bearer_token': bearerToken - }, - callback: function(r) { - if (r.message && r.message.success) { - if (r.message.authenticated) { - ETaxesCompany._setDialogSuccess(dialog, - __('Authentication Successful'), - __('You are now authenticated with Asan Imza')); - - setTimeout(() => callback(true), 1000); - } else { - setTimeout(pollStep, pollInterval); - } - } else { - ETaxesCompany._setDialogError(dialog, - __('Authentication Error'), - r.message ? r.message.message : __('An unknown error occurred')); - callback(false); - } - }, - error: function() { - setTimeout(pollStep, pollInterval); - } - }); - } - - pollStep(); - }, - - /** - * Complete authentication process - */ - completeAuthentication: function(dialog, asanLoginName, callback) { - ETaxesCompany._updateLoadingDialog(dialog, - __('Completing Authentication'), - __('Getting certificates and selecting taxpayer...')); - - frappe.call({ - method: 'taxes_az.auth.get_auth_certificates', - args: { 'asan_login_name': asanLoginName }, - callback: function(certR) { - if (certR.message && certR.message.success) { - // Try to use existing certificate or select first available - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Asan Login', - name: asanLoginName - }, - callback: function(r) { - if (r.message && r.message.selected_certificate_json) { - // Use existing certificate - const certData = JSON.parse(r.message.selected_certificate_json); - const certName = r.message.selected_certificate; - - ETaxesCompany.selectCertificateAndTaxpayer(dialog, asanLoginName, certData, certName, callback); - } else if (certR.message.certificates && certR.message.certificates.length > 0) { - // Use first available certificate - const cert = certR.message.certificates[0]; - 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 1'; - } - - ETaxesCompany.selectCertificateAndTaxpayer(dialog, asanLoginName, cert, certName, callback); - } else { - ETaxesCompany._setDialogError(dialog, - __('No Certificates'), - __('No certificates found for this account')); - } - } - }); - } else { - ETaxesCompany._setDialogError(dialog, - __('Error'), - certR.message ? certR.message.message : __('Failed to get certificates')); - } - } - }); - }, - - /** - * Select certificate and taxpayer - */ - selectCertificateAndTaxpayer: function(dialog, asanLoginName, certData, certName, callback) { - ETaxesCompany._updateLoadingDialog(dialog, - __('Selecting certificate'), - certName); - - frappe.call({ - method: 'taxes_az.auth.select_certificate', - args: { - 'asan_login_name': asanLoginName, - 'certificate_data': certData, - 'certificate_name': certName - }, - callback: function(r) { - if (r.message && r.message.success) { - ETaxesCompany._updateLoadingDialog(dialog, - __('Selecting taxpayer'), - __('Using certificate: ') + certName); - - frappe.call({ - method: 'taxes_az.auth.select_taxpayer', - args: { 'asan_login_name': asanLoginName }, - callback: function(r) { - if (r.message && r.message.success) { - ETaxesCompany._setDialogSuccess(dialog, - __('Authentication Complete'), - __('You can now access E-Taxes services')); - - setTimeout(callback, 1000); - } else { - ETaxesCompany._setDialogError(dialog, - __('Error'), - r.message ? r.message.message : __('Failed to select taxpayer')); - } - } - }); - } else { - ETaxesCompany._setDialogError(dialog, - __('Error'), - r.message ? r.message.message : __('Failed to select certificate')); - } - } - }); - }, - - /** - * Fetch company profile from E-Taxes - */ - fetchProfile: function(frm) { - frappe.call({ - method: 'taxes_az.company_etaxes.get_company_profile', - callback: function(r) { - ETaxesCompany._handleProfileResponse(r, frm); - }, - error: function(xhr, status, error) { - frappe.msgprint({ - title: __('Network Error'), - message: __('Failed to connect to server: {0}', [error || 'Unknown error']), - indicator: 'red' - }); - } - }); - }, - - /** - * Общая функция для скрытия загрузочного диалога - */ - _hideLoadingDialog: function() { - if (ETaxesCompany.currentLoadingDialog) { - try { - ETaxesCompany.currentLoadingDialog.hide(); - } catch (e) { - console.error('Error hiding loading dialog:', e); - } - ETaxesCompany.currentLoadingDialog = null; - } - }, - - /** - * Handle profile response - */ - _handleProfileResponse: function(r, frm) { - if (r.message && r.message.success) { - ETaxesCompany.showProfileDialog(r.message.data, frm); - } else if (r.message && r.message.error === 'unauthorized') { - frappe.confirm( - __('Your E-Taxes session has expired. Would you like to authenticate again?'), - function() { - ETaxesCompany.startInlineAuthentication(frm); - }, - function() { - frappe.show_alert({ - message: __('Authentication cancelled'), - indicator: 'red' - }, 3); - } - ); - } else if (r.message && r.message.error === 'server_error') { - frappe.msgprint({ - title: __('Server Error'), - message: __('E-Taxes service is temporarily unavailable. Please try again in a few minutes.'), - indicator: 'orange' - }); - } else { - frappe.msgprint({ - title: __('Error'), - message: r.message ? r.message.message : __('An error occurred while fetching profile data'), - indicator: 'red' - }); - } - }, - - /** - * Show enhanced profile dialog with automatic parsing - */ - showProfileDialog: function(profileData, frm) { - const profile = profileData.profile || profileData; - const parsedInfo = ETaxesCompany._formatParsedProfileHTML(profile); - - const dialog = new frappe.ui.Dialog({ - title: __('E-Taxes Company Profile'), - size: 'large', - fields: [ - { - fieldname: 'parsed_info', - fieldtype: 'HTML', - label: __('Company Information') - } - ], - primary_action_label: __('Update Company Fields'), - primary_action: function() { - dialog.hide(); // Hide dialog before starting update - ETaxesCompany.updateCompanyFromProfile(profileData, frm); - } - }); - - // Set parsed information HTML - dialog.fields_dict.parsed_info.$wrapper.html(parsedInfo); - - dialog.show(); - }, - - /** - * Получить справочник налоговых органов - */ - _getTaxAuthorities: function() { - return new Promise((resolve, reject) => { - // Проверяем кэш - if (ETaxesCompany._dictionaryCache.taxAuthorities) { - resolve(ETaxesCompany._dictionaryCache.taxAuthorities); - return; - } - - // Запрашиваем справочник через Python API - frappe.call({ - method: 'taxes_az.company_etaxes.get_tax_authorities', - callback: function(r) { - if (r.message && r.message.success) { - // Извлекаем массив authorities из data - const authoritiesArray = r.message.data.authorities || r.message.data; - ETaxesCompany._dictionaryCache.taxAuthorities = authoritiesArray; - resolve(authoritiesArray); - } else { - reject(r.message ? r.message.message : 'Failed to load tax authorities'); - } - }, - error: function() { - reject('Failed to load tax authorities'); - } - }); - }); - }, - - /** - * Получить справочник типов собственности - */ - _getPropertyTypes: function() { - return new Promise((resolve, reject) => { - // Проверяем кэш - if (ETaxesCompany._dictionaryCache.propertyTypes) { - let cachedData = ETaxesCompany._dictionaryCache.propertyTypes; - - // Исправляем структуру кэша если данные вложены неправильно - if (Array.isArray(cachedData) && cachedData.length === 1 && Array.isArray(cachedData[0])) { - cachedData = cachedData[0]; - ETaxesCompany._dictionaryCache.propertyTypes = cachedData; - } - - resolve(cachedData); - return; - } - - // Запрашиваем справочник через Python API - frappe.call({ - method: 'taxes_az.company_etaxes.get_property_types', - callback: function(r) { - if (r.message && r.message.success) { - const propertyTypesData = r.message.data; - let propertyTypesArray = []; - - if (Array.isArray(propertyTypesData)) { - propertyTypesArray = propertyTypesData; - } else if (typeof propertyTypesData === 'object' && propertyTypesData !== null) { - // Преобразуем объект в массив - propertyTypesArray = Object.values(propertyTypesData); - } - - ETaxesCompany._dictionaryCache.propertyTypes = propertyTypesArray; - resolve(propertyTypesArray); - } else { - reject(r.message ? r.message.message : 'Failed to load property types'); - } - }, - error: function() { - reject('Failed to load property types'); - } - }); - }); - }, - - /** - * Найти название налогового органа по коду - */ - _getTaxAuthorityName: function(code) { - return new Promise((resolve) => { - ETaxesCompany._getTaxAuthorities().then((authorities) => { - if (authorities && Array.isArray(authorities)) { - const authority = authorities.find(auth => auth.code === code); - if (authority && authority.name && authority.name.az) { - resolve(authority.name.az); - } else if (authority && authority.name && typeof authority.name === 'string') { - resolve(authority.name); - } else { - resolve(code); - } - } else { - resolve(code); - } - }).catch(() => { - resolve(code); - }); - }); - }, - - /** - * Найти название типа собственности по коду - */ - _getPropertyTypeName: function(code) { - return new Promise((resolve) => { - ETaxesCompany._getPropertyTypes().then((propertyTypes) => { - let dataToSearch = propertyTypes; - - // Дополнительная проверка структуры данных - if (Array.isArray(propertyTypes) && propertyTypes.length === 1 && Array.isArray(propertyTypes[0])) { - dataToSearch = propertyTypes[0]; - } - - if (dataToSearch && Array.isArray(dataToSearch)) { - const propertyType = dataToSearch.find(prop => prop.code === code); - if (propertyType && propertyType.name && propertyType.name.az) { - resolve(propertyType.name.az); - } else if (propertyType && propertyType.name && typeof propertyType.name === 'string') { - resolve(propertyType.name); - } else { - resolve(code); - } - } else { - resolve(code); - } - }).catch(() => { - resolve(code); - }); - }); - }, - - /** - * Parse profile data and extract field mappings - */ - _parseProfileData: function(profile, frm) { - const updates = []; - - // Helper function to add update if value is different - function addUpdate(fieldname, newValue, label, oldValue) { - // Специальная обработка для checkbox полей - if (typeof newValue === 'boolean' || fieldname.includes('is_') || fieldname.includes('has_') || fieldname.includes('special_tax_regime')) { - // Преобразуем оба значения к boolean для корректного сравнения - const newBool = Boolean(newValue); - const oldBool = Boolean(oldValue); - - // Сравниваем как boolean - if (newBool !== oldBool) { - updates.push({ - fieldname: fieldname, - newValue: newBool ? '1' : '0', - oldValue: oldBool ? '1' : '0', - label: label - }); - } - } else { - // Для остальных полей - обычная логика - if (newValue !== null && newValue !== undefined && newValue !== oldValue) { - newValue = String(newValue).trim(); - if (newValue !== '' && newValue !== String(oldValue || '').trim()) { - updates.push({ - fieldname: fieldname, - newValue: newValue, - oldValue: oldValue || '', - label: label - }); - } - } - } - } - - // Helper function to format date from YYYYMMDD to YYYY-MM-DD - function formatDate(dateStr) { - if (!dateStr || dateStr.length !== 8) return null; - const year = dateStr.substring(0, 4); - const month = dateStr.substring(4, 6); - const day = dateStr.substring(6, 8); - return `${year}-${month}-${day}`; - } - - // BASIC COMPANY INFORMATION - // Tax ID mapping (TIN) - if (profile.tin) { - addUpdate('tax_id', profile.tin, __('Tax ID'), frm.doc.tax_id); - } - - // Registration Number mapping - if (profile.stateRegistrationDocumentNumber) { - addUpdate('registration_details', profile.stateRegistrationDocumentNumber, __('Registration Details'), frm.doc.registration_details); - } - - // Phone mapping - prefer mobile over landline - const mobilePhone = profile.mobilePhoneNumber || ETaxesCompany._extractPhoneNumber(profile, 'mobile'); - const landlinePhone = profile.landlinePhoneNumber || ETaxesCompany._extractPhoneNumber(profile, 'landline'); - - if (mobilePhone) { - addUpdate('phone_no', mobilePhone, __('Phone'), frm.doc.phone_no); - } else if (landlinePhone) { - addUpdate('phone_no', landlinePhone, __('Phone'), frm.doc.phone_no); - } - - // Store both phones in dedicated fields - if (mobilePhone) { - addUpdate('mobile_phone', mobilePhone, __('Mobile Phone'), frm.doc.mobile_phone); - } - if (landlinePhone) { - addUpdate('landline_phone', landlinePhone, __('Landline Phone'), frm.doc.landline_phone); - } - - // Email mapping - if (profile.email) { - addUpdate('email', profile.email, __('Email'), frm.doc.email); - } - - // Registration Date mapping - if (profile.taxpayerRegistrationDate) { - const regDate = moment(profile.taxpayerRegistrationDate).format('YYYY-MM-DD'); - addUpdate('date_of_establishment', regDate, __('Date of Establishment'), frm.doc.date_of_establishment); - } - - // State Registration Document Issued Date - if (profile.stateRegistrationDocumentIssuedDate) { - const issuedDate = moment(profile.stateRegistrationDocumentIssuedDate).format('YYYY-MM-DD'); - addUpdate('state_registration_document_issued_date', issuedDate, __('State Registration Document Issued Date'), frm.doc.state_registration_document_issued_date); - } - - // Domain mapping (if available) - if (profile.mainActivity) { - addUpdate('domain', profile.mainActivity, __('Domain'), frm.doc.domain); - } - - // MANAGEMENT INFORMATION - // Director Name from E-Taxes - if (profile.companyDirectorName) { - addUpdate('director_name_etaxes', profile.companyDirectorName, __('Director Name (E-Taxes)'), frm.doc.director_name_etaxes); - } - - // Director PIN - if (profile.pin) { - addUpdate('director_pin', profile.pin, __('Director PIN'), frm.doc.director_pin); - } - - // BUSINESS ACTIVITIES - // Main Activity mapping - if (profile.mainActivity) { - addUpdate('main_activity', profile.mainActivity, __('Main Activity'), frm.doc.main_activity); - } - - // Legal Form Code - if (profile.legalFormCode) { - addUpdate('legal_form_code', profile.legalFormCode, __('Legal Form Code'), frm.doc.legal_form_code); - } - - // TAX INFORMATION - // Taxation System mapping - if (profile.taxRate) { - let taxationSystem = ''; - if (profile.taxRate === 'ƏDV' || profile.taxRate === 'VAT' || profile.taxRate === '18%') { - taxationSystem = '18% Value-added tax'; - } else if (profile.taxRate === '0%' || profile.taxRate === 'ƏDV 0%') { - taxationSystem = '0% Value-added tax'; - } else { - taxationSystem = profile.taxRate; - } - addUpdate('taxation_system', taxationSystem, __('Taxation System'), frm.doc.taxation_system); - } - - // Business Classification mapping - if (profile.criteriaOfBusinessEntity) { - let businessClassification = ''; - - switch (profile.criteriaOfBusinessEntity.toLowerCase()) { - case 'micro': - businessClassification = 'Micro Entrepreneur'; - break; - case 'small': - businessClassification = 'Small Entrepreneur'; - break; - case 'middle': - case 'medium': - businessClassification = 'Medium Entrepreneur'; - break; - case 'big': - case 'large': - businessClassification = 'Big Entrepreneur'; - break; - default: - businessClassification = profile.criteriaOfBusinessEntity + ' Entrepreneur'; - } - - addUpdate('business_classification', businessClassification, __('Business Classification'), frm.doc.business_classification); - } - - // State Registration Authority - if (profile.stateRegistrationName) { - addUpdate('state_registration_authority', profile.stateRegistrationName, __('State Registration Authority'), frm.doc.state_registration_authority); - } - - // Taxpayer Activity Group - if (profile.activityGroup) { - addUpdate('taxpayer_activity_group', profile.activityGroup, __('Taxpayer Activity Group'), frm.doc.taxpayer_activity_group); - } - - // Special Tax Regime - if (profile.specialTaxRegime !== undefined) { - addUpdate('special_tax_regime', profile.specialTaxRegime, __('Special Tax Regime'), frm.doc.special_tax_regime); - } - - // TIN Type - if (profile.tinType) { - addUpdate('tin_type', profile.tinType, __('TIN Type'), frm.doc.tin_type); - } - - // Is Risky - if (profile.isRisky !== undefined) { - addUpdate('is_risky_taxpayer', profile.isRisky, __('Is Risky Taxpayer'), frm.doc.is_risky_taxpayer); - } - - // Sport Betting Operator - if (profile.sportBettingOperator !== undefined) { - addUpdate('is_sport_betting_operator', profile.sportBettingOperator, __('Sport Betting Operator'), frm.doc.is_sport_betting_operator); - } - - // Has Active Production Object - if (profile.hasActiveProductionObject !== undefined) { - addUpdate('has_active_production_object', profile.hasActiveProductionObject, __('Has Active Production Object'), frm.doc.has_active_production_object); - } - - // Tax System Type - if (profile.taxSystemType) { - addUpdate('tax_system_type', profile.taxSystemType, __('Tax System Type'), frm.doc.tax_system_type); - } - - // Is Taxpayer in Cancellation Process - if (profile.isTaxPayerInCancellationProcess !== undefined) { - addUpdate('is_taxpayer_in_cancellation_process', profile.isTaxPayerInCancellationProcess, __('Is Taxpayer in Cancellation Process'), frm.doc.is_taxpayer_in_cancellation_process); - } - - // Is Chief of Any Legal Entity - if (profile.isChiefOfAnyLegalEntity !== undefined) { - addUpdate('is_chief_of_any_legal_entity', profile.isChiefOfAnyLegalEntity, __('Is Chief of Any Legal Entity'), frm.doc.is_chief_of_any_legal_entity); - } - - // VAT INFORMATION - if (profile.vatInfo && typeof profile.vatInfo === 'object') { - // VAT Registration Date - if (profile.vatInfo.prOperationTableOperationDate) { - const vatRegDate = formatDate(profile.vatInfo.prOperationTableOperationDate); - if (vatRegDate) { - addUpdate('vat_registration_date', vatRegDate, __('VAT Registration Date'), frm.doc.vat_registration_date); - } - } - - // VAT Certificate Number - if (profile.vatInfo.recDocumentDocumentNumber) { - addUpdate('vat_certificate_number', profile.vatInfo.recDocumentDocumentNumber, __('VAT Certificate Number'), frm.doc.vat_certificate_number); - } - - // VAT Certificate Date - if (profile.vatInfo.prVatOperationsRegDate) { - const vatCertDate = formatDate(profile.vatInfo.prVatOperationsRegDate); - if (vatCertDate) { - addUpdate('vat_certificate_date', vatCertDate, __('VAT Certificate Date'), frm.doc.vat_certificate_date); - } - } - } - - // ORGANIZATIONAL STRUCTURE - // Parent Organization - if (profile.headOrganizationName && Array.isArray(profile.headOrganizationName) && profile.headOrganizationName.length > 0) { - const parentOrgText = profile.headOrganizationName.join(', '); - addUpdate('parent_organization', parentOrgText, __('Parent Organization'), frm.doc.parent_organization); - } - - // Parent Organization TIN - if (profile.headOrganizationTin) { - addUpdate('parent_organization_tin', profile.headOrganizationTin, __('Parent Organization TIN'), frm.doc.parent_organization_tin); - } - - // Employee Count - if (profile.employeeData && profile.employeeData.employeeCount !== undefined) { - addUpdate('employee_count', profile.employeeData.employeeCount, __('Employee Count'), frm.doc.employee_count); - } - - // REGISTRATION DATES - // Suspension Dates - if (profile.suspensionStartDate) { - const suspStartDate = moment(profile.suspensionStartDate).format('YYYY-MM-DD'); - addUpdate('suspension_start_date', suspStartDate, __('Suspension Start Date'), frm.doc.suspension_start_date); - } - - if (profile.suspensionEndDate) { - const suspEndDate = moment(profile.suspensionEndDate).format('YYYY-MM-DD'); - addUpdate('suspension_end_date', suspEndDate, __('Suspension End Date'), frm.doc.suspension_end_date); - } - - // Liquidation Date - if (profile.liquidationDate) { - const liqDate = moment(profile.liquidationDate).format('YYYY-MM-DD'); - addUpdate('liquidation_date', liqDate, __('Liquidation Date'), frm.doc.liquidation_date); - } - - // ADDRESS INFORMATION - // Legal Address Full - if (profile.legalAddress) { - addUpdate('legal_address_full', profile.legalAddress, __('Legal Address (Full)'), frm.doc.legal_address_full); - } - - // Actual Address Full - if (profile.actualAddress) { - addUpdate('actual_address_full', profile.actualAddress, __('Actual Address (Full)'), frm.doc.actual_address_full); - } - - // Address for Mail - if (profile.addressForMail) { - addUpdate('address_for_mail', profile.addressForMail, __('Address for Mail'), frm.doc.address_for_mail); - } - - // Legal Address Details - if (profile.legalAddressObject && typeof profile.legalAddressObject === 'object') { - if (profile.legalAddressObject.postcode) { - addUpdate('legal_address_postcode', profile.legalAddressObject.postcode, __('Legal Address Postcode'), frm.doc.legal_address_postcode); - } - if (profile.legalAddressObject.region) { - addUpdate('legal_address_region', profile.legalAddressObject.region, __('Legal Address Region'), frm.doc.legal_address_region); - } - if (profile.legalAddressObject.locality) { - addUpdate('legal_address_locality', profile.legalAddressObject.locality, __('Legal Address Locality'), frm.doc.legal_address_locality); - } - if (profile.legalAddressObject.street) { - addUpdate('legal_address_street', profile.legalAddressObject.street, __('Legal Address Street'), frm.doc.legal_address_street); - } - if (profile.legalAddressObject.houseNumber) { - addUpdate('legal_address_house', profile.legalAddressObject.houseNumber, __('Legal Address House Number'), frm.doc.legal_address_house); - } - if (profile.legalAddressObject.roomNumber) { - addUpdate('legal_address_room', profile.legalAddressObject.roomNumber, __('Legal Address Room Number'), frm.doc.legal_address_room); - } - } - - // TAX SYSTEMS LIST - if (profile.taxSystems && Array.isArray(profile.taxSystems) && profile.taxSystems.length > 0) { - const taxSystemsList = profile.taxSystems.join(', '); - addUpdate('tax_systems_list', taxSystemsList, __('Tax Systems List'), frm.doc.tax_systems_list); - } - - // EMPLOYER INFORMATION - if (profile.employer && typeof profile.employer === 'object') { - if (profile.employer.name) { - addUpdate('employer_name', profile.employer.name, __('Employer Name'), frm.doc.employer_name); - } - if (profile.employer.position) { - addUpdate('employer_position', profile.employer.position, __('Employer Position'), frm.doc.employer_position); - } - } - - return updates; - }, - - updateCompanyFromProfile: function(profileData, frm) { - try { - const profile = profileData.profile || profileData; - const updates = ETaxesCompany._parseProfileData(profile, frm); - - const hasAdditionalActivities = profile.additionalActivityCodes && Array.isArray(profile.additionalActivityCodes) && profile.additionalActivityCodes.length > 0; - const hasAffiliates = (profile.affiliatesNames && profile.affiliatesNames.length > 0) || (profile.affiliatesTins && profile.affiliatesTins.length > 0); - const hasDictionaryFields = profile.taxAuthorityCode || profile.propertyType; - - if (updates.length > 0 || hasAdditionalActivities || hasAffiliates || hasDictionaryFields) { - let confirmMessage = ''; - - if (updates.length > 0) { - confirmMessage = __('The following company fields will be updated with data from E-Taxes:') + '

'; - let changesList = ''; - confirmMessage += changesList; - } - - let additionalInfo = ''; - if (hasAdditionalActivities) { - additionalInfo += `
${__('Additional activities to be added')}: ${profile.additionalActivityCodes.length} ${__('activities')}`; - additionalInfo += '
' + profile.additionalActivityCodes.join(', ') + ''; - } - if (hasAffiliates) { - const affiliateCount = Math.max( - (profile.affiliatesNames || []).length, - (profile.affiliatesTins || []).length - ); - additionalInfo += `
${__('Affiliate organizations to be added')}: ${affiliateCount} ${__('organizations')}`; - } - - if (!confirmMessage && additionalInfo) { - confirmMessage = __('The following data will be updated:'); - } - - confirmMessage += additionalInfo; - confirmMessage += '

' + __('Continue?'); - - frappe.confirm( - confirmMessage, - function() { - // Показываем индикатор загрузки - frappe.show_alert({ - message: __('Updating company data...'), - indicator: 'blue' - }); - - // Собираем все обновления полей - let allUpdates = {}; - updates.forEach(update => { - allUpdates[update.fieldname] = update.newValue; - }); - - // Обрабатываем справочники - let promises = []; - - if (profile.taxAuthorityCode) { - promises.push( - ETaxesCompany._getTaxAuthorityName(profile.taxAuthorityCode).then((authorityName) => { - allUpdates['tax_authority'] = authorityName; - }) - ); - } - - if (profile.propertyType) { - promises.push( - ETaxesCompany._getPropertyTypeName(profile.propertyType).then((propertyTypeName) => { - allUpdates['property_type'] = propertyTypeName; - }) - ); - } - - Promise.all(promises).then(() => { - // Подготавливаем данные для отправки - const updateData = { - company_name: frm.doc.name, - field_updates: JSON.stringify(allUpdates), - additional_activities: JSON.stringify(hasAdditionalActivities ? profile.additionalActivityCodes : []), - affiliate_names: JSON.stringify(hasAffiliates ? (profile.affiliatesNames || []) : []), - affiliate_tins: JSON.stringify(hasAffiliates ? (profile.affiliatesTins || []) : []) - }; - - console.log('Sending update data:', updateData); - - // Вызываем серверный метод для форсированного обновления - frappe.call({ - method: 'taxes_az.company_etaxes.force_update_company', - args: updateData, - callback: function(r) { - console.log('Update response:', r); - - if (r.message && r.message.success) { - // Перезагружаем форму - frm.reload_doc(); - - let successMessage = __('Company data successfully updated from E-Taxes profile'); - if (r.message.additional_activities_added > 0) { - successMessage += '
' + __('Added {0} additional activities', [r.message.additional_activities_added]); - } - if (r.message.affiliates_added > 0) { - successMessage += '
' + __('Added {0} affiliate organizations', [r.message.affiliates_added]); - } - - frappe.show_alert({ - message: successMessage, - indicator: 'green' - }, 5); - } else { - frappe.msgprint({ - title: __('Update Error'), - message: r.message ? r.message.message : __('Error updating company data'), - indicator: 'red' - }); - } - }, - error: function(r) { - console.error('Update error:', r); - frappe.msgprint({ - title: __('Server Error'), - message: __('Server error during update. Please check the error logs.'), - indicator: 'red' - }); - } - }); - }).catch(error => { - console.error('Error processing dictionaries:', error); - frappe.msgprint({ - title: __('Processing Error'), - message: __('Error processing dictionary data: {0}', [error]), - indicator: 'red' - }); - }); - }, - function() { - frappe.show_alert({ - message: __('Update cancelled'), - indicator: 'blue' - }, 3); - } - ); - } else { - frappe.show_alert({ - message: __('No new data to update'), - indicator: 'blue' - }, 3); - } - - } catch (e) { - console.error('Error parsing profile data:', e); - frappe.show_alert({ - message: __('Error parsing profile data: {0}', [e.message]), - indicator: 'red' - }, 5); - } - }, - - /** - * Extract best phone number from profile - */ - _extractPhoneNumber: function(profile, preferredType = null) { - // If preferredType is specified, try to get that type first - if (preferredType === 'mobile' && profile.mobilePhoneNumber) { - return profile.mobilePhoneNumber; - } - if (preferredType === 'landline' && profile.landlinePhoneNumber) { - return profile.landlinePhoneNumber; - } - - // Priority: mobile > landline > contacts mobile > contacts home - if (profile.mobilePhoneNumber) { - return profile.mobilePhoneNumber; - } - - if (profile.landlinePhoneNumber) { - return profile.landlinePhoneNumber; - } - - // Check contacts array - if (profile.contacts && Array.isArray(profile.contacts)) { - // Look for mobile first - const mobileContact = profile.contacts.find(c => c.type === 'mobile' && c.contact); - if (mobileContact) { - return mobileContact.prefix ? `${mobileContact.prefix}${mobileContact.contact}` : mobileContact.contact; - } - - // Then home phone - const phoneContact = profile.contacts.find(c => c.type === 'homePhone' && c.contact); - if (phoneContact) { - return phoneContact.prefix ? `${phoneContact.prefix}${phoneContact.contact}` : phoneContact.contact; - } - } - - return null; - }, - - /** - * Format VAT Info object as readable text - */ - _formatVatInfo: function(vatInfo) { - if (!vatInfo || typeof vatInfo !== 'object') { - return vatInfo; - } - - let formatted = ''; - if (vatInfo.recDocumentDocumentNumber) { - formatted += __('Document No') + ': ' + vatInfo.recDocumentDocumentNumber; - } - if (vatInfo.prVatOperationsRegDate) { - if (formatted) formatted += '
'; - formatted += __('Registration Date') + ': ' + vatInfo.prVatOperationsRegDate; - } - if (vatInfo.prOperationTableOperationDate) { - if (formatted) formatted += '
'; - formatted += __('Operation Date') + ': ' + vatInfo.prOperationTableOperationDate; - } - - return formatted || JSON.stringify(vatInfo); - }, - - /** - * Format parsed profile data as HTML - */ - _formatParsedProfileHTML: function(profile) { - let html = '
'; - - try { - // Basic Company Information - html += '
' + __('Company Information') + '
'; - html += ''; - - if (profile.companyName) { - html += ``; - } - if (profile.tin) { - html += ``; - } - if (profile.pin) { - html += ``; - } - if (profile.stateRegistrationDocumentNumber) { - html += ``; - } - if (profile.taxpayerRegistrationDate) { - const regDate = moment(profile.taxpayerRegistrationDate).format('DD.MM.YYYY'); - html += ``; - } - if (profile.stateRegistrationDocumentIssuedDate) { - const issuedDate = moment(profile.stateRegistrationDocumentIssuedDate).format('DD.MM.YYYY'); - html += ``; - } - if (profile.taxpayerStatus) { - const statusText = profile.taxpayerStatus === 'A' ? __('Active') : profile.taxpayerStatus; - html += ``; - } - if (profile.legalFormCode) { - html += ``; - } - if (profile.tinType) { - html += ``; - } - - html += '
${__('Company Name')}${profile.companyName}
${__('TIN')}${profile.tin}
${__('PIN')}${profile.pin}
${__('Registration Number')}${profile.stateRegistrationDocumentNumber}
${__('Registration Date')}${regDate}
${__('State Registration Document Issued Date')}${issuedDate}
${__('Status')}${statusText}
${__('Legal Form Code')}${profile.legalFormCode}
${__('TIN Type')}${profile.tinType}
'; - - // Contact Information - html += '
' + __('Contact Information') + '
'; - html += ''; - - if (profile.mobilePhoneNumber) { - html += ``; - } - if (profile.landlinePhoneNumber) { - html += ``; - } - if (profile.email) { - html += ``; - } - if (profile.legalAddress) { - html += ``; - } - if (profile.actualAddress) { - html += ``; - } - if (profile.addressForMail) { - html += ``; - } - - // Legal Address Details - if (profile.legalAddressObject && typeof profile.legalAddressObject === 'object') { - const addr = profile.legalAddressObject; - if (addr.postcode || addr.region || addr.locality || addr.street || addr.houseNumber || addr.roomNumber) { - html += ``; - if (addr.postcode) html += ``; - if (addr.region) html += ``; - if (addr.locality) html += ``; - if (addr.street) html += ``; - if (addr.houseNumber) html += ``; - if (addr.roomNumber) html += ``; - } - } - - html += '
${__('Mobile Phone')}${profile.mobilePhoneNumber}
${__('Landline Phone')}${profile.landlinePhoneNumber}
${__('Email')}${profile.email}
${__('Legal Address')}${profile.legalAddress}
${__('Actual Address')}${profile.actualAddress}
${__('Address for Mail')}${profile.addressForMail}
${__('Legal Address Details')}
${__('Postcode')}${addr.postcode}
${__('Region')}${addr.region}
${__('Locality')}${addr.locality}
${__('Street')}${addr.street}
${__('House Number')}${addr.houseNumber}
${__('Room Number')}${addr.roomNumber}
'; - - // Business Information - html += '
' + __('Business Information') + '
'; - html += ''; - - if (profile.mainActivity) { - html += ``; - } - if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) { - html += ``; - } - if (profile.criteriaOfBusinessEntity) { - html += ``; - } - if (profile.activityGroup) { - html += ``; - } - if (profile.sportBettingOperator !== undefined) { - html += ``; - } - if (profile.hasActiveProductionObject !== undefined) { - html += ``; - } - - html += '
${__('Main Activity Code')}${profile.mainActivity}
${__('Additional Activity Codes')}${profile.additionalActivityCodes.join(', ')}
${__('Business Entity Criteria')}${profile.criteriaOfBusinessEntity}
${__('Activity Group')}${profile.activityGroup}
${__('Sport Betting Operator')}${profile.sportBettingOperator ? __('Yes') : __('No')}
${__('Has Active Production Object')}${profile.hasActiveProductionObject ? __('Yes') : __('No')}
'; - - // Tax Information - html += '
' + __('Tax Information') + '
'; - html += ''; - - if (profile.taxRate) { - html += ``; - } - if (profile.specialTaxRegime !== undefined) { - html += ``; - } - if (profile.taxAuthorityCode) { - html += ``; - } - if (profile.propertyType) { - html += ``; - } - if (profile.taxSystemType) { - html += ``; - } - if (profile.taxSystems && profile.taxSystems.length > 0) { - html += ``; - } - if (profile.isRisky !== undefined) { - html += ``; - } - if (profile.isTaxPayerInCancellationProcess !== undefined) { - html += ``; - } - if (profile.vatInfo) { - const formattedVat = ETaxesCompany._formatVatInfo(profile.vatInfo); - html += ``; - } - - html += '
${__('Tax Rate')}${profile.taxRate}
${__('Special Tax Regime')}${profile.specialTaxRegime ? __('Yes') : __('No')}
${__('Tax Authority Code')}${profile.taxAuthorityCode}
${__('Property Type')}${profile.propertyType}
${__('Tax System Type')}${profile.taxSystemType}
${__('Tax Systems')}${profile.taxSystems.join(', ')}
${__('Is Risky Taxpayer')}${profile.isRisky ? __('Yes') : __('No')}
${__('In Cancellation Process')}${profile.isTaxPayerInCancellationProcess ? __('Yes') : __('No')}
${__('VAT Info')}${formattedVat}
'; - - // Organizational Structure - html += '
' + __('Organizational Structure') + '
'; - html += ''; - - if (profile.headOrganizationName && profile.headOrganizationName.length > 0) { - html += ``; - } - if (profile.headOrganizationTin) { - html += ``; - } - if (profile.affiliatesNames && profile.affiliatesNames.length > 0) { - const affiliatesText = profile.affiliatesNames.join(', '); - html += ``; - } - if (profile.affiliatesTins && profile.affiliatesTins.length > 0) { - const affiliatesTinsText = profile.affiliatesTins.join(', '); - html += ``; - } - - html += '
${__('Parent Organization')}${profile.headOrganizationName.join(', ')}
${__('Parent Organization TIN')}${profile.headOrganizationTin}
${__('Affiliate Organizations')}${affiliatesText}
${__('Affiliate Organizations TINs')}${affiliatesTinsText}
'; - - // Management Information - html += '
' + __('Management Information') + '
'; - html += ''; - - if (profile.companyDirectorName) { - html += ``; - } - if (profile.chief) { - const chiefName = `${profile.chief.name || ''} ${profile.chief.surname || ''} ${profile.chief.middleName || ''}`.trim(); - if (chiefName) { - html += ``; - } - if (profile.chief.pin) { - html += ``; - } - } - if (profile.employer && profile.employer.name) { - html += ``; - if (profile.employer.position) { - html += ``; - } - } - if (profile.isChiefOfAnyLegalEntity !== undefined) { - html += ``; - } - if (profile.employeeData && profile.employeeData.employeeCount !== undefined) { - html += ``; - } - - html += '
${__('Director Name')}${profile.companyDirectorName}
${__('Chief')}${chiefName}
${__('Chief PIN')}${profile.chief.pin}
${__('Employer')}${profile.employer.name}
${__('Employer Position')}${profile.employer.position}
${__('Is Chief of Any Legal Entity')}${profile.isChiefOfAnyLegalEntity ? __('Yes') : __('No')}
${__('Employee Count')}${profile.employeeData.employeeCount}
'; - - // Important Dates - if (profile.suspensionStartDate || profile.suspensionEndDate || profile.liquidationDate) { - html += '
' + __('Important Dates') + '
'; - html += ''; - - if (profile.suspensionStartDate) { - const suspStartDate = moment(profile.suspensionStartDate).format('DD.MM.YYYY'); - html += ``; - } - if (profile.suspensionEndDate) { - const suspEndDate = moment(profile.suspensionEndDate).format('DD.MM.YYYY'); - html += ``; - } - if (profile.liquidationDate) { - const liqDate = moment(profile.liquidationDate).format('DD.MM.YYYY'); - html += ``; - } - - html += '
${__('Suspension Start Date')}${suspStartDate}
${__('Suspension End Date')}${suspEndDate}
${__('Liquidation Date')}${liqDate}
'; - } - - // Certificates Information (if any) - if (profile.certificates && profile.certificates.length > 0) { - html += '
' + __('Certificates') + '
'; - html += ''; - - profile.certificates.forEach((cert, index) => { - html += ``; - if (cert.taxpayerType) { - html += ``; - } - if (cert.legalInfo) { - if (cert.legalInfo.tin) html += ``; - if (cert.legalInfo.name) html += ``; - } - if (cert.individualInfo) { - if (cert.individualInfo.fin) html += ``; - if (cert.individualInfo.name) html += ``; - } - if (cert.position) { - html += ``; - } - if (cert.hasAccess !== undefined) { - html += ``; - } - if (cert.liquidated !== undefined) { - html += ``; - } - }); - - html += '
${__('Certificate')} ${index + 1}
${__('Type')}${cert.taxpayerType}
${__('TIN')}${cert.legalInfo.tin}
${__('Name')}${cert.legalInfo.name}
${__('FIN')}${cert.individualInfo.fin}
${__('Name')}${cert.individualInfo.name}
${__('Position')}${cert.position}
${__('Has Access')}${cert.hasAccess ? __('Yes') : __('No')}
${__('Liquidated')}${cert.liquidated ? __('Yes') : __('No')}
'; - } - - } catch (e) { - html += '
' + __('Unable to parse profile data: {0}', [e.message]) + '
'; - } - - html += '
'; - - // CSS остается тем же - html = ` - - ` + html; - - return html; - }, - - /** - * Helper functions for authentication dialogs - */ - _getAuthLoadingHTML: function(title, message) { - return ` -
-
-
-
- -
-

${title || __('Please wait...')}

-

${message || __('The operation may take some time')}

-
- -
- `; - }, - - _updateLoadingDialog: function(dialog, title, message) { - if (title) $('#auth_loading_title').text(title); - if (message) $('#auth_loading_message').text(message); - }, - - _showVerificationCode: function(dialog, code) { - if (code) { - $('#auth_verification_code').text(code); - $('#auth_verification_code_container').show(); - } - }, - - _setDialogSuccess: function(dialog, title, message) { - dialog.$wrapper.find('.loading-animation').html(` -
- -
- `); - - $('#auth_loading_title').text(title); - $('#auth_loading_message').text(message); - $('#auth_verification_code_container').hide(); - }, - - _setDialogError: function(dialog, title, message) { - dialog.$wrapper.find('.loading-animation').html(` -
- -
- `); - - $('#auth_loading_title').text(title); - $('#auth_loading_message').text(message); - $('#auth_verification_code_container').hide(); - - dialog.set_primary_action(__('Close'), () => dialog.hide()); } }; @@ -1794,4 +310,4 @@ function build_wizard_html(frm, year_wizards, period_wizards) { html += ``; frm.fields_dict.tax_wizards_html.$wrapper.html(html); -} \ No newline at end of file +}