diff --git a/taxes_az/client/company.js b/taxes_az/client/company.js index 2011298..a015816 100644 --- a/taxes_az/client/company.js +++ b/taxes_az/client/company.js @@ -551,161 +551,332 @@ window.ETaxesCompany = { }); }, - /** - * Parse profile data and extract field mappings - * ПОЛНАЯ ВЕРСИЯ: Включает все поля включая VAT и новые поля - */ - _parseProfileData: function(profile, frm) { - const updates = []; - - // Helper function to add update if value is different - function addUpdate(fieldname, newValue, label, oldValue) { - if (newValue && newValue !== oldValue && newValue.trim() !== '') { - updates.push({ - fieldname: fieldname, - newValue: newValue.trim(), - 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}`; - } - - // 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 - const phoneNumber = ETaxesCompany._extractPhoneNumber(profile); - if (phoneNumber) { - addUpdate('phone_no', phoneNumber, __('Phone'), frm.doc.phone_no); - } - - // 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); - } - - // Domain mapping (if available) - if (profile.mainActivity) { - addUpdate('domain', profile.mainActivity, __('Domain'), frm.doc.domain); - } + /** + * 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); + } - // КАСТОМНЫЕ ПОЛЯ - - // Main Activity mapping (custom field) - if (profile.mainActivity) { - addUpdate('main_activity', profile.mainActivity, __('Main Activity'), frm.doc.main_activity); - } - - // Taxation System mapping (custom field) - 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 (custom field) - if (profile.criteriaOfBusinessEntity) { - let businessClassification = ''; - - // Преобразование значений E-Taxes в правильные названия - 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: - // Если не распознали, используем исходное значение с добавлением " Entrepreneur" - 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); - } - - // VAT ПОЛЯ - if (profile.vatInfo && typeof profile.vatInfo === 'object') { - // VAT Registration Date (prOperationTableOperationDate) - 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 (recDocumentDocumentNumber) - if (profile.vatInfo.recDocumentDocumentNumber) { - addUpdate('vat_certificate_number', profile.vatInfo.recDocumentDocumentNumber, __('VAT Certificate Number'), frm.doc.vat_certificate_number); - } - - // VAT Certificate Date (prVatOperationsRegDate) - 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); - } - } - } - - // ОРГАНИЗАЦИОННАЯ СТРУКТУРА - - // Parent Organization (из headOrganizationName) - 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); - } - - return updates; - }, + // 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; + }, /** * НОВАЯ ФУНКЦИЯ: Обновляем поля с названиями из справочников @@ -726,296 +897,370 @@ window.ETaxesCompany = { } }, - /** - * ИСПРАВЛЕННАЯ ВЕРСИЯ: Обновляем Additional Activity Types из additionalActivityCodes - * с проверкой существования в Main type of activity - */ - _updateAdditionalActivities: function(profile, frm) { - if (profile.additionalActivityCodes && Array.isArray(profile.additionalActivityCodes) && profile.additionalActivityCodes.length > 0) { - // Очищаем существующие записи в табличной части Additional Activity Types - frm.clear_table('additional_activity_types'); - - let addedCount = 0; - let skippedCount = 0; - let processedCount = 0; - - // Функция для проверки и добавления одного кода активности - function processActivityCode(activityCode, callback) { - if (activityCode && activityCode.trim()) { - const trimmedCode = activityCode.trim(); - - // Проверяем существование записи Main type of activity перед добавлением - frappe.call({ - method: 'frappe.client.get_value', - args: { - doctype: 'Main type of activity', - fieldname: 'name', - filters: {name: trimmedCode} - }, - callback: function(r) { - processedCount++; - - if (r.message && r.message.name) { - // Запись существует - добавляем - let row = frm.add_child('additional_activity_types'); - row.activity_type = trimmedCode; - addedCount++; - } else { - // Запись не существует - пропускаем - skippedCount++; - } - - // Вызываем callback когда обработка завершена - if (callback) callback(); - }, - error: function() { - processedCount++; - skippedCount++; - if (callback) callback(); - } - }); - } else { - processedCount++; - skippedCount++; - if (callback) callback(); - } - } - - // Функция для последовательной обработки всех кодов - function processAllCodes(codes, index = 0) { - if (index >= codes.length) { - // Все коды обработаны - обновляем UI и показываем результат - frm.refresh_field('additional_activity_types'); - - let message = ''; - if (addedCount > 0) { - message = __('Added {0} additional activities', [addedCount]); - if (skippedCount > 0) { - message += __(', skipped {0} (not found)', [skippedCount]); - } - frappe.show_alert({ - message: message, - indicator: 'green' - }, 4); - } else { - message = __('No valid additional activities found'); - if (skippedCount > 0) { - message += __(', {0} codes not found in system', [skippedCount]); - } - frappe.show_alert({ - message: message, - indicator: 'orange' - }, 4); - } - - return; - } - - // Обрабатываем текущий код и переходим к следующему - processActivityCode(codes[index], function() { - processAllCodes(codes, index + 1); - }); - } - - // Запускаем обработку всех кодов - processAllCodes(profile.additionalActivityCodes); - - } else { - frappe.show_alert({ - message: __('No additional activities to process'), - indicator: 'blue' - }, 3); - } - }, + _updateAdditionalActivities: function(profile, frm) { + if (profile.additionalActivityCodes && Array.isArray(profile.additionalActivityCodes) && profile.additionalActivityCodes.length > 0) { + // ВАЖНО: Не очищаем таблицу сразу, а работаем с существующими данными + const existingActivities = frm.doc.additional_activity_types || []; + const existingCodes = existingActivities.map(row => row.activity_type); + + let addedCount = 0; + let skippedCount = 0; + let duplicateCount = 0; + + // Функция для проверки и добавления одного кода активности + function processActivityCode(activityCode, callback) { + if (activityCode && activityCode.trim()) { + const trimmedCode = activityCode.trim(); + + // Проверяем, не добавлен ли уже этот код + if (existingCodes.includes(trimmedCode)) { + duplicateCount++; + if (callback) callback(); + return; + } + + // Проверяем существование записи Main type of activity перед добавлением + frappe.call({ + method: 'frappe.client.get_value', + args: { + doctype: 'Main type of activity', + fieldname: 'name', + filters: {name: trimmedCode} + }, + callback: function(r) { + if (r.message && r.message.name) { + // Запись существует - добавляем + let row = frm.add_child('additional_activity_types'); + row.activity_type = trimmedCode; + addedCount++; + } else { + // Запись не существует - пропускаем + skippedCount++; + } + + if (callback) callback(); + }, + error: function() { + skippedCount++; + if (callback) callback(); + } + }); + } else { + skippedCount++; + if (callback) callback(); + } + } + + // Функция для последовательной обработки всех кодов + function processAllCodes(codes, index = 0) { + if (index >= codes.length) { + // Все коды обработаны - обновляем UI + frm.refresh_field('additional_activity_types'); + + let message = ''; + if (addedCount > 0) { + message = __('Added {0} additional activities', [addedCount]); + if (skippedCount > 0) { + message += __(', skipped {0} (not found)', [skippedCount]); + } + if (duplicateCount > 0) { + message += __(', {0} already existed', [duplicateCount]); + } + frappe.show_alert({ + message: message, + indicator: 'green' + }, 4); + } else if (duplicateCount > 0) { + message = __('All {0} activities already exist', [duplicateCount]); + frappe.show_alert({ + message: message, + indicator: 'blue' + }, 4); + } else { + message = __('No valid additional activities found'); + if (skippedCount > 0) { + message += __(', {0} codes not found in system', [skippedCount]); + } + frappe.show_alert({ + message: message, + indicator: 'orange' + }, 4); + } + + return; + } + + // Обрабатываем текущий код и переходим к следующему + processActivityCode(codes[index], function() { + processAllCodes(codes, index + 1); + }); + } + + // Запускаем обработку всех кодов + processAllCodes(profile.additionalActivityCodes); + + } else { + frappe.show_alert({ + message: __('No additional activities to process'), + indicator: 'blue' + }, 3); + } + }, - /** - * ОБНОВЛЕННАЯ ФУНКЦИЯ: Обновляем дочерние организации из affiliatesNames - */ - _updateAffiliateOrganizations: function(profile, frm) { - if (profile.affiliatesNames && Array.isArray(profile.affiliatesNames) && profile.affiliatesNames.length > 0) { - // Очищаем существующие записи - frm.clear_table('affiliate_organizations'); - - let addedCount = 0; - - // Добавляем каждую дочернюю организацию из affiliatesNames - profile.affiliatesNames.forEach(function(orgName, index) { - if (orgName && orgName.trim()) { - let row = frm.add_child('affiliate_organizations'); - - // Имя заполняем из массива affiliatesNames - row.organization_name = orgName.trim(); - - addedCount++; - } - }); - - // Обновляем отображение табличной части - frm.refresh_field('affiliate_organizations'); - - if (addedCount > 0) { - frappe.show_alert({ - message: __('Added {0} affiliate organizations', [addedCount]), - indicator: 'green' - }, 3); - } - } else { - // Очищаем таблицу если нет дочерних организаций - frm.clear_table('affiliate_organizations'); - frm.refresh_field('affiliate_organizations'); - } - }, + _updateAffiliateOrganizations: function(profile, frm) { + // Проверяем наличие дочерних организаций + const hasAffiliateNames = profile.affiliatesNames && Array.isArray(profile.affiliatesNames) && profile.affiliatesNames.length > 0; + const hasAffiliateTins = profile.affiliatesTins && Array.isArray(profile.affiliatesTins) && profile.affiliatesTins.length > 0; + + if (hasAffiliateNames || hasAffiliateTins) { + // Очищаем существующие записи в табличной части + frm.clear_table('affiliate_organizations'); + + let addedCount = 0; + let skippedCount = 0; + + // Определяем максимальную длину массивов + const maxLength = Math.max( + hasAffiliateNames ? profile.affiliatesNames.length : 0, + hasAffiliateTins ? profile.affiliatesTins.length : 0 + ); + + // Обрабатываем каждую организацию + for (let i = 0; i < maxLength; i++) { + // Получаем имя организации + let orgName = ''; + if (hasAffiliateNames && profile.affiliatesNames[i]) { + orgName = profile.affiliatesNames[i].trim(); + } + + // Получаем TIN организации + let orgTin = ''; + if (hasAffiliateTins && profile.affiliatesTins[i]) { + orgTin = profile.affiliatesTins[i].trim(); + } + + // Добавляем строку только если есть хотя бы имя или TIN + if (orgName || orgTin) { + let row = frm.add_child('affiliate_organizations'); + + // Заполняем имя организации + if (orgName) { + row.organization_name = orgName; + } else { + // Если нет имени, но есть TIN, используем TIN как имя + row.organization_name = `Organization (TIN: ${orgTin})`; + } + + // Заполняем TIN если есть + if (orgTin) { + row.organization_tin = orgTin; + } + + addedCount++; + + // Логируем для отладки + console.log(`Added affiliate organization: ${row.organization_name} (TIN: ${row.organization_tin || 'N/A'})`); + } else { + skippedCount++; + console.log(`Skipped empty affiliate organization at index ${i}`); + } + } + + // Обновляем отображение табличной части + frm.refresh_field('affiliate_organizations'); + + // Показываем результат + if (addedCount > 0) { + let message = __('Added {0} affiliate organization(s)', [addedCount]); + if (skippedCount > 0) { + message += __(', skipped {0} empty record(s)', [skippedCount]); + } + + frappe.show_alert({ + message: message, + indicator: 'green' + }, 4); + + // Дополнительное логирование + console.log(`Affiliate organizations update completed: ${addedCount} added, ${skippedCount} skipped`); + } else { + frappe.show_alert({ + message: __('No valid affiliate organizations found to add'), + indicator: 'orange' + }, 3); + } + + } else { + // Нет дочерних организаций - очищаем таблицу + if (frm.doc.affiliate_organizations && frm.doc.affiliate_organizations.length > 0) { + frm.clear_table('affiliate_organizations'); + frm.refresh_field('affiliate_organizations'); + + frappe.show_alert({ + message: __('Cleared affiliate organizations (none found in E-Taxes)'), + indicator: 'blue' + }, 3); + } + + console.log('No affiliate organizations found in profile data'); + } + + // Дополнительная проверка консистентности данных + if (hasAffiliateNames && hasAffiliateTins) { + if (profile.affiliatesNames.length !== profile.affiliatesTins.length) { + console.warn(`Warning: Mismatch in affiliate data - ${profile.affiliatesNames.length} names vs ${profile.affiliatesTins.length} TINs`); + + // Можно показать предупреждение пользователю + frappe.show_alert({ + message: __('Warning: Number of affiliate names and TINs do not match'), + indicator: 'yellow' + }, 5); + } + } + }, - /** - * Update company fields from profile data with automatic parsing - * ОБНОВЛЕННАЯ ВЕРСИЯ: с новыми полями и справочниками - */ - updateCompanyFromProfile: function(profileData, frm) { - try { - const profile = profileData.profile || profileData; - const updates = ETaxesCompany._parseProfileData(profile, frm); - - if (updates.length > 0) { - // Show confirmation with list of changes - let changesList = ''; - - // Добавляем информацию о дополнительных данных - let additionalInfo = ''; - if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) { - additionalInfo += `
${__('Additional activities to be added')}: ${profile.additionalActivityCodes.length} ${__('activities')}`; - } - if (profile.affiliatesNames && profile.affiliatesNames.length > 0) { - additionalInfo += `
${__('Affiliate organizations to be added')}: ${profile.affiliatesNames.length} ${__('organizations')}`; - } - - frappe.confirm( - __('The following company fields will be updated with data from E-Taxes:') + - '

' + changesList + additionalInfo + - '
' + __('Continue?'), - function() { - // Apply all updates - updates.forEach(update => { - frm.set_value(update.fieldname, update.newValue); - }); - - // Обновляем дополнительные активности - ETaxesCompany._updateAdditionalActivities(profile, frm); - - // Обновляем дочерние организации - ETaxesCompany._updateAffiliateOrganizations(profile, frm); - - // НОВОЕ: Обновляем поля из справочников - ETaxesCompany._updateDictionaryFields(profile, frm); - - frappe.show_alert({ - message: __('Company data updated from E-Taxes profile ({0} fields)', [updates.length]), - indicator: 'green' - }, 5); - - // Сохраняем изменения - frm.save(); - }, - function() { - frappe.show_alert({ - message: __('Update cancelled'), - indicator: 'blue' - }, 3); - } - ); - } else { - // Проверяем есть ли дополнительные данные для обновления - const hasAdditionalActivities = profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0; - const hasAffiliates = profile.affiliatesNames && profile.affiliatesNames.length > 0; - const hasDictionaryFields = profile.taxAuthorityCode || profile.propertyType; - - if (hasAdditionalActivities || hasAffiliates || hasDictionaryFields) { - let updateInfo = __('The following data will be updated:'); - if (hasAdditionalActivities) { - updateInfo += `
${__('Additional activities')}: ${profile.additionalActivityCodes.length} ${__('activities')}`; - } - if (hasAffiliates) { - updateInfo += `
${__('Affiliate organizations')}: ${profile.affiliatesNames.length} ${__('organizations')}`; - } - if (hasDictionaryFields) { - updateInfo += `
${__('Dictionary fields')}: Tax Authority, Property Type`; - } - - frappe.confirm( - updateInfo + '

' + __('Continue?'), - function() { - if (hasAdditionalActivities) { - ETaxesCompany._updateAdditionalActivities(profile, frm); - } - if (hasAffiliates) { - ETaxesCompany._updateAffiliateOrganizations(profile, frm); - } - if (hasDictionaryFields) { - ETaxesCompany._updateDictionaryFields(profile, frm); - } - - // Автосохранение после добавления данных - setTimeout(function() { - frm.save(); - }, 500); - }, - 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); - } - }, + updateCompanyFromProfile: function(profileData, frm) { + try { + const profile = profileData.profile || profileData; + const updates = ETaxesCompany._parseProfileData(profile, frm); + + // Проверяем есть ли дополнительные данные для обновления + const hasAdditionalActivities = profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0; + const hasAffiliates = profile.affiliatesNames && profile.affiliatesNames.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')}`; + } + if (hasAffiliates) { + additionalInfo += `
${__('Affiliate organizations to be added')}: ${profile.affiliatesNames.length} ${__('organizations')}`; + } + + if (!confirmMessage && additionalInfo) { + confirmMessage = __('The following data will be updated:'); + } + + confirmMessage += additionalInfo; + confirmMessage += '
' + __('Continue?'); + + frappe.confirm( + confirmMessage, + function() { + // ВАЖНО: Отключаем автосохранение перед массовыми изменениями + frm.disable_save(); + + // Apply all field updates + updates.forEach(update => { + frm.set_value(update.fieldname, update.newValue); + }); + + // Обновляем поля из справочников + if (hasDictionaryFields) { + ETaxesCompany._updateDictionaryFields(profile, frm); + } + + // Задержка перед обновлением табличных частей + setTimeout(function() { + // Обновляем дополнительные активности + if (hasAdditionalActivities) { + ETaxesCompany._updateAdditionalActivities(profile, frm); + } + + // Обновляем дочерние организации + if (hasAffiliates) { + ETaxesCompany._updateAffiliateOrganizations(profile, frm); + } + + // Включаем сохранение обратно и сохраняем + setTimeout(function() { + frm.enable_save(); + frm.save('Update', function() { + frappe.show_alert({ + message: __('Company data updated from E-Taxes profile'), + indicator: 'green' + }, 5); + }); + }, 500); + }, 300); + }, + 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) { - // 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; - }, + _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 @@ -1044,221 +1289,332 @@ window.ETaxesCompany = { /** * 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.taxpayerStatus) { - html += ``; - } - - html += '
${__('Company Name')}${profile.companyName}
${__('TIN')}${profile.tin}
${__('PIN')}${profile.pin}
${__('Registration Number')}${profile.stateRegistrationDocumentNumber}
${__('Registration Date')}${regDate}
${__('Status')}${profile.taxpayerStatus}
'; - - // Contact Information - const phoneNumber = ETaxesCompany._extractPhoneNumber(profile); - const address = profile.legalAddress || profile.actualAddress; - - if (phoneNumber || profile.email || address) { - html += '
' + __('Contact Information') + '
'; - html += ''; - - if (phoneNumber) { - html += ``; - } - if (profile.email) { - html += ``; - } - if (address) { - html += ``; - } - - html += '
${__('Phone')}${phoneNumber}
${__('Email')}${profile.email}
${__('Address')}${address}
'; - } - - // Business Information - if (profile.mainActivity || profile.additionalActivityCodes || profile.legalFormCode || profile.criteriaOfBusinessEntity) { - html += '
' + __('Business Information') + '
'; - html += ''; - - if (profile.mainActivity) { - html += ``; - } - if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) { - html += ``; - } - if (profile.legalFormCode) { - html += ``; - } - if (profile.criteriaOfBusinessEntity) { - html += ``; - } - - html += '
${__('Main Activity Code')}${profile.mainActivity}
${__('Additional Activity Codes')}${profile.additionalActivityCodes.join(', ')}
${__('Legal Form Code')}${profile.legalFormCode}
${__('Business Entity Criteria')}${profile.criteriaOfBusinessEntity}
'; - } - - // Tax Information - if (profile.taxRate || profile.vatInfo || profile.specialTaxRegime !== undefined || profile.taxAuthorityCode || profile.propertyType) { - html += '
' + __('Tax Information') + '
'; - html += ''; - - if (profile.taxRate) { - html += ``; - } - if (profile.specialTaxRegime !== undefined) { - html += ``; - } - if (profile.taxAuthorityCode) { - html += ``; - } - if (profile.propertyType) { - 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}
${__('VAT Info')}${formattedVat}
'; - } - - // Organizational Structure - if (profile.affiliatesNames) { - html += '
' + __('Organizational Structure') + '
'; - html += ''; - - if (profile.affiliatesNames && profile.affiliatesNames.length > 0) { - const affiliatesText = profile.affiliatesNames.join(', '); - html += ``; - } - - html += '
${__('Affiliate Organizations')}${affiliatesText}
'; - } - - // Director Information - if (profile.companyDirectorName || profile.chief) { - 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.employeeData && profile.employeeData.employeeCount) { - html += ``; - } - - html += '
${__('Director Name')}${profile.companyDirectorName}
${__('Chief')}${chiefName}
${__('Chief PIN')}${profile.chief.pin}
${__('Employee Count')}${profile.employeeData.employeeCount}
'; - } - - } catch (e) { - html += '
' + __('Unable to parse profile data: {0}', [e.message]) + '
'; - } - - html += '
'; - - // CSS с нормальным переносом строк - html = ` - - ` + html; - - return 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 diff --git a/taxes_az/taxes_az/doctype/business_classification/business_classification.json b/taxes_az/taxes_az/doctype/business_classification/business_classification.json index 5ec2bad..5f0976e 100644 --- a/taxes_az/taxes_az/doctype/business_classification/business_classification.json +++ b/taxes_az/taxes_az/doctype/business_classification/business_classification.json @@ -1,21 +1,21 @@ { "actions": [], - "creation": "2025-06-05 14:25:30.955525", + "creation": "2025-06-16 21:29:11.777085", "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "section_break_t7i9" + "section_break_twaq" ], "fields": [ { - "fieldname": "section_break_t7i9", + "fieldname": "section_break_twaq", "fieldtype": "Section Break" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-06-05 14:34:53.228349", + "modified": "2025-06-16 21:29:44.750246", "modified_by": "Administrator", "module": "Taxes Az", "name": "Business Classification", diff --git a/taxes_az/taxes_az/doctype/company_additional_activity/__init__.py b/taxes_az/taxes_az/doctype/company_additional_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.json b/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.json new file mode 100644 index 0000000..4eb3ea4 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.json @@ -0,0 +1,33 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-16 17:05:37.052445", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity_type" + ], + "fields": [ + { + "fieldname": "activity_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Activity Type", + "options": "Main type of activity", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-16 17:05:37.052445", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Company Additional Activity", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.py b/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.py new file mode 100644 index 0000000..e2028ae --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_additional_activity/company_additional_activity.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompanyAdditionalActivity(Document): + pass diff --git a/taxes_az/taxes_az/doctype/company_affiliate_organization/__init__.py b/taxes_az/taxes_az/doctype/company_affiliate_organization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.json b/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.json new file mode 100644 index 0000000..b658926 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-16 17:05:37.121462", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "organization_name", + "organization_tin" + ], + "fields": [ + { + "columns": 8, + "fieldname": "organization_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Organization Name", + "reqd": 1 + }, + { + "columns": 4, + "fieldname": "organization_tin", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Organization TIN" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-18 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Company Affiliate Organization", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.py b/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.py new file mode 100644 index 0000000..8880bbc --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_affiliate_organization/company_affiliate_organization.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompanyAffiliateOrganization(Document): + pass diff --git a/taxes_az/taxes_az/doctype/company_business_object/__init__.py b/taxes_az/taxes_az/doctype/company_business_object/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/company_business_object/company_business_object.json b/taxes_az/taxes_az/doctype/company_business_object/company_business_object.json new file mode 100644 index 0000000..b8f3930 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_business_object/company_business_object.json @@ -0,0 +1,90 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-16 17:05:37.233923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "object_name", + "object_code", + "object_address", + "main_area", + "additional_area", + "activity_classification", + "main_activity_code", + "total_employees", + "temporary_employees" + ], + "fields": [ + { + "fieldname": "object_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Object Name", + "reqd": 1 + }, + { + "fieldname": "object_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Object Code", + "reqd": 1 + }, + { + "fieldname": "object_address", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Object Address" + }, + { + "fieldname": "main_area", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Main Area (sq.m)" + }, + { + "fieldname": "additional_area", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Additional Area (sq.m)" + }, + { + "fieldname": "activity_classification", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Activity Classification", + "options": "P\u0259rak\u0259nd\u0259 ticar\u0259t\nTopdan ticar\u0259t\n\u0130ctimai ia\u015f\u0259\nEmaledici s\u0259naye\nTikinti\nXidm\u0259t\nDig\u0259r" + }, + { + "fieldname": "main_activity_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Main Activity Code" + }, + { + "fieldname": "total_employees", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Total Employees" + }, + { + "fieldname": "temporary_employees", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Temporary Employees" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-16 17:05:37.233923", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Company Business Object", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/company_business_object/company_business_object.py b/taxes_az/taxes_az/doctype/company_business_object/company_business_object.py new file mode 100644 index 0000000..bf052a6 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_business_object/company_business_object.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompanyBusinessObject(Document): + pass diff --git a/taxes_az/taxes_az/doctype/company_certificate_data/__init__.py b/taxes_az/taxes_az/doctype/company_certificate_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.json b/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.json new file mode 100644 index 0000000..aed9aaa --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.json @@ -0,0 +1,72 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-16 17:05:37.403337", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "certificate_type", + "entity_name", + "entity_voen", + "certificate_series", + "certificate_number", + "service_value" + ], + "fields": [ + { + "fieldname": "certificate_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Certificate Type", + "options": "Issued\nReceived", + "reqd": 1 + }, + { + "fieldname": "entity_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Entity Name", + "reqd": 1 + }, + { + "fieldname": "entity_voen", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Entity V\u00d6EN", + "reqd": 1 + }, + { + "fieldname": "certificate_series", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Certificate Series", + "length": 3 + }, + { + "fieldname": "certificate_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Certificate Number" + }, + { + "description": "Only for received certificates", + "fieldname": "service_value", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Service Value" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-16 17:05:37.403337", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Company Certificate Data", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.py b/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.py new file mode 100644 index 0000000..fced6c5 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_certificate_data/company_certificate_data.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompanyCertificateData(Document): + pass diff --git a/taxes_az/taxes_az/doctype/company_non_resident_service/__init__.py b/taxes_az/taxes_az/doctype/company_non_resident_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.json b/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.json new file mode 100644 index 0000000..73f1e08 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.json @@ -0,0 +1,93 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-06-16 17:05:37.524930", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "non_resident_name", + "country_of_residence", + "service_description", + "service_period", + "foreign_voen", + "payment_document_number", + "payment_date", + "amount_without_vat", + "vat_amount" + ], + "fields": [ + { + "fieldname": "non_resident_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Non-Resident Name", + "reqd": 1 + }, + { + "fieldname": "country_of_residence", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Country of Residence", + "options": "T\u00fcrkiy\u0259\nRusiya\nG\u00fcrc\u00fcstan\nAlmaniya\n\u0130ngilt\u0259r\u0259\nAB\u015e\nDig\u0259r", + "reqd": 1 + }, + { + "fieldname": "service_description", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Service Description", + "reqd": 1 + }, + { + "fieldname": "service_period", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Service Period" + }, + { + "fieldname": "foreign_voen", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Foreign V\u00d6EN" + }, + { + "fieldname": "payment_document_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Payment Document Number" + }, + { + "fieldname": "payment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Payment Date" + }, + { + "fieldname": "amount_without_vat", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount Without VAT", + "reqd": 1 + }, + { + "fieldname": "vat_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "VAT Amount", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-06-16 17:05:37.524930", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Company Non Resident Service", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.py b/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.py new file mode 100644 index 0000000..4281a38 --- /dev/null +++ b/taxes_az/taxes_az/doctype/company_non_resident_service/company_non_resident_service.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompanyNonResidentService(Document): + pass diff --git a/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.js b/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.js index ff5785d..142cd8f 100644 --- a/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.js +++ b/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.js @@ -1,33 +1,424 @@ -// Код для экспорта XML frappe.ui.form.on('Declaration of value added tax', { refresh: function(frm) { - frm.add_custom_button(__('Export XML'), function() { - frappe.call({ - method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml', - args: { - mapping_tool: 'Declaration of value added tax', // Замените на имя вашего документа XML Mapping Tool - doctype: 'Declaration of value added tax', // Замените на имя DocType - docname: frm.docname - }, - callback: function(r) { - if (r.message) { - // Создаем временный элемент для скачивания - var element = document.createElement('a'); - element.setAttribute('href', 'data:text/xml;charset=utf-8,' + encodeURIComponent(r.message)); - element.setAttribute('download', frm.docname + '.xml'); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - - frappe.show_alert({ - message: __('XML file generated and downloaded'), - indicator: 'green' - }); - } - } - }); - }, __('Actions')); + // Добавляем кнопку автозаполнения + if (!frm.is_new() && frm.doc.docstatus === 0) { + frm.add_custom_button(__('Avtomatik Doldur'), function() { + fill_declaration_automatically(frm); + }, __('Əməliyyatlar')); + + // Стилизуем кнопку + frm.page.set_primary_action(__('Avtomatik Doldur'), function() { + fill_declaration_automatically(frm); + }); + } + + // Добавляем кнопку проверки + if (!frm.is_new()) { + frm.add_custom_button(__('Yoxla'), function() { + validate_declaration(frm); + }, __('Əməliyyatlar')); + } + + // Скрываем/показываем вкладки в зависимости от данных + toggle_tabs_visibility(frm); + }, + + onload: function(frm) { + // Устанавливаем значения по умолчанию + if (frm.is_new()) { + set_default_values(frm); + } + + // Настраиваем фильтры для полей + setup_field_filters(frm); + }, + + rüb: function(frm) { + // При изменении квартала обновляем период + update_period_display(frm); + }, + + il: function(frm) { + // При изменении года обновляем период + update_period_display(frm); + }, + + bəyan_ediləcək_məlumatım_yoxdur: function(frm) { + // Если отмечено "нет данных для декларирования" + if (frm.doc.bəyan_ediləcək_məlumatım_yoxdur) { + frappe.confirm( + 'Bütün məlumatlar silinəcək. Davam etmək istəyirsiniz?', + function() { + clear_all_tables(frm); + }, + function() { + frm.set_value('bəyan_ediləcək_məlumatım_yoxdur', 0); + } + ); } } -); \ No newline at end of file +}); + +// Функция автоматического заполнения +function fill_declaration_automatically(frm) { + // Проверяем обязательные поля + if (!frm.doc.rüb || !frm.doc.il) { + frappe.msgprint({ + title: __('Xəta'), + indicator: 'red', + message: __('Zəhmət olmasa rüb və il seçin') + }); + return; + } + + // Получаем компанию + let company = frm.doc.company || frappe.defaults.get_user_default("Company"); + + if (!company) { + frappe.msgprint({ + title: __('Xəta'), + indicator: 'red', + message: __('Şirkət seçilməyib') + }); + return; + } + + // Показываем индикатор загрузки + frappe.show_progress('Doldurulur', 30, 100, 'Məlumatlar yüklənir...'); + + // Вызываем серверную функцию + frappe.call({ + method: 'taxes_az.declaration_of_value_added_tax.fill_declaration_of_value_added_tax', + args: { + declaration_name: frm.doc.name, + company: company, + quarter: frm.doc.rüb, + year: frm.doc.il + }, + callback: function(r) { + frappe.hide_progress(); + + if (r.message && r.message.status === 'success') { + frm.reload_doc(); + + // Показываем результат + let msg = ` +
+

Bəyannamə uğurla dolduruldu!

+

Dövriyyə: ${format_currency(r.message.total_turnover)} AZN

+

Hesablanmış ƏDV: ${format_currency(r.message.total_vat)} AZN

+

Əvəzləşdirilən ƏDV: ${format_currency(r.message.offset_vat)} AZN

+
+

${r.message.total_vat_payable >= 0 ? 'Büdcəyə ödənilməli' : 'Büdcədən qaytarılmalı'}: + ${format_currency(Math.abs(r.message.total_vat_payable))} AZN

+
+ `; + + frappe.msgprint({ + title: __('Nəticə'), + indicator: 'green', + message: msg + }); + + // Обновляем видимость вкладок + toggle_tabs_visibility(frm); + } else { + frappe.msgprint({ + title: __('Xəta'), + indicator: 'red', + message: r.message.message || 'Xəta baş verdi' + }); + } + }, + error: function(err) { + frappe.hide_progress(); + frappe.msgprint({ + title: __('Xəta'), + indicator: 'red', + message: __('Server xətası: ') + err.message + }); + } + }); +} + +// Функция проверки декларации +function validate_declaration(frm) { + let errors = []; + let warnings = []; + + // Проверка основных данных + if (!frm.doc.vöen) { + errors.push('VÖEN doldurulmayıb'); + } + + // Проверка таблиц + if (!frm.doc.table_qzfk || frm.doc.table_qzfk.length === 0) { + errors.push('Hesabat dövriyyəsi cədvəli boşdur'); + } + + // Проверка расчетов + let total_vat = 0; + let offset_vat = 0; + + // Считаем начисленный НДС + (frm.doc.table_qzfk || []).forEach(row => { + if (row.göstəricilər && row.göstəricilər.startsWith('301')) { + total_vat += (row.ədvməbləği || 0); + } + }); + + // Считаем зачетный НДС + (frm.doc.table_zvvh || []).forEach(row => { + if (row.göstəricilər && row.göstəricilər !== '317. Əməliyyatlar üzrə CƏMİ') { + offset_vat += (row.əlavədəyərvergisininməbləği || 0); + } + }); + + // Проверка итоговой строки + let has_payable = false; + let has_refund = false; + + (frm.doc.table_kyya || []).forEach(row => { + if (row.göstəricilər === '326. Büdcəyə ödənilməlidir') { + has_payable = true; + if (row.ədvməbləği !== (total_vat - offset_vat)) { + errors.push('Büdcəyə ödənilməli məbləğ düzgün hesablanmayıb'); + } + } + if (row.göstəricilər === '327. Büdcədən qaytarılır') { + has_refund = true; + } + }); + + if (has_payable && has_refund) { + errors.push('Həm ödəniş, həm də qaytarma eyni vaxtda ola bilməz'); + } + + // Проверка приложений + if (frm.doc.table_gpfh && frm.doc.table_gpfh.length > 0) { + frm.doc.table_gpfh.forEach((row, idx) => { + if (!row.obyektinadı || !row.obyektinünvanı) { + warnings.push(`Əlavə 9: Sətir ${idx + 1} - obyekt məlumatları natamamdır`); + } + }); + } + + // Показываем результаты + show_validation_results(errors, warnings); +} + +// Установка значений по умолчанию +function set_default_values(frm) { + // Текущий квартал + let current_month = frappe.datetime.now_date().getMonth() + 1; + let current_quarter = Math.ceil(current_month / 3); + let quarter_map = {1: '1. Rüb', 2: '2. Rüb', 3: '3. Rüb', 4: '4. Rüb'}; + + frm.set_value('rüb', quarter_map[current_quarter]); + frm.set_value('il', frappe.datetime.now_date().getFullYear()); + frm.set_value('vergidövrü', 'Rüblük'); + frm.set_value('bəyannaməninnövü', 'Cari'); + + // Данные компании + let company = frappe.defaults.get_user_default("Company"); + if (company) { + frappe.db.get_doc('Company', company).then(doc => { + frm.set_value('vöen', doc.tax_id); + frm.set_value('ödəyicitipi', doc.taxpayer_type || 'Hüquqi'); + frm.set_value('vergininödəyicisininqrupu', doc.taxpayer_group); + frm.set_value('vergiödəyisininqrupu', doc.tax_payment_group); + frm.set_value('vergiorqanı', doc.tax_authority); + frm.set_value('valyutanınnövü', 'Azərbaycan manatı'); + + // НДС регистрация + if (doc.vat_registration_date) { + frm.set_value('ədvqeydiyyatınınqüvəyyəminmətarixi', + frappe.datetime.str_to_user(doc.vat_registration_date).replace(/-/g, '')); + } + if (doc.vat_certificate_number) { + frm.set_value('ədvbildirişininnsi', doc.vat_certificate_number); + } + }); + } +} + +// Настройка фильтров +function setup_field_filters(frm) { + // Фильтр для выбора месяца в зависимости от квартала + frm.set_query('ay', function() { + let quarter_months = { + '1. Rüb': ['Yanvar', 'Fevral', 'Mart'], + '2. Rüb': ['Aprel', 'May', 'İyun'], + '3. Rüb': ['İyul', 'Avqust', 'Sentyabr'], + '4. Rüb': ['Oktyabr', 'Noyabr', 'Dekabr'] + }; + + return { + filters: [ + ['name', 'in', quarter_months[frm.doc.rüb] || []] + ] + }; + }); +} + +// Скрытие/показ вкладок +function toggle_tabs_visibility(frm) { + // Скрываем все вкладки приложений по умолчанию + let all_appendices = [ + 'əlavə_1_tab', 'əlavə_2_tab', 'əlavə_3_tab', 'əlavə_4_tab', + 'əlavə_5_tab', 'əlavə_6_tab', 'əlavə_7_tab', 'əlavə_8_tab', + 'əlavə_9_tab', 'əlavə_10_tab' + ]; + + all_appendices.forEach(tab => { + frm.toggle_display(tab, false); + }); + + // Показываем только заполненные + // Приложение 3 - всегда для услуг + if (frm.doc.table_kdll && frm.doc.table_kdll.length > 0) { + frm.toggle_display('əlavə_3_tab', true); + } + + // Приложение 7 - услуги нерезидентов + if (frm.doc.table_vonn && frm.doc.table_vonn.length > 0) { + frm.toggle_display('əlavə_7_tab', true); + } + + // Приложение 9 - объекты + if (frm.doc.table_gpfh && frm.doc.table_gpfh.length > 0) { + frm.toggle_display('əlavə_9_tab', true); + } +} + +// Обновление отображения периода +function update_period_display(frm) { + if (frm.doc.rüb && frm.doc.il) { + let period_text = `${frm.doc.il}-ci il, ${frm.doc.rüb}`; + frm.set_intro(period_text); + } +} + +// Очистка всех таблиц +function clear_all_tables(frm) { + let tables = [ + 'table_qzfk', 'table_ufli', 'table_zvvh', 'table_wtjt', + 'table_kyya', 'table_ftqr', 'table_rsid', 'table_vliw', + 'table_fdbw', 'table_anob', 'table_kdll', 'table_pycw', + 'table_hnwx', 'table_bchc', 'table_vonn', 'table_gpfh', + 'table_bgay', 'table_hnbv', 'table_kmis', 'table_twws' + ]; + + tables.forEach(table => { + frm.clear_table(table); + }); + + frm.refresh_fields(); + frappe.show_alert({ + message: __('Bütün cədvəllər təmizləndi'), + indicator: 'blue' + }); +} + +// Показ результатов валидации +function show_validation_results(errors, warnings) { + let html = '
'; + + if (errors.length > 0) { + html += '

Xətalar:

'; + } + + if (warnings.length > 0) { + html += '

Xəbərdarlıqlar:

'; + } + + if (errors.length === 0 && warnings.length === 0) { + html += '

Heç bir xəta tapılmadı!

'; + } + + html += '
'; + + frappe.msgprint({ + title: __('Yoxlama nəticəsi'), + indicator: errors.length > 0 ? 'red' : (warnings.length > 0 ? 'orange' : 'green'), + message: html + }); +} + +// Форматирование валюты +function format_currency(amount) { + return frappe.format(amount, {fieldtype: 'Currency', currency: 'AZN'}); +} + +// Дополнительные обработчики для таблиц +frappe.ui.form.on('Declaration of value added tax Hesabat Dovriyye uzre', { + ədvməbləği: function(frm, cdt, cdn) { + calculate_totals(frm); + } +}); + +frappe.ui.form.on('Declaration of value added tax Evezlesdirilen', { + əlavədəyərvergisininməbləği: function(frm, cdt, cdn) { + calculate_totals(frm); + } +}); + +// Пересчет итогов +function calculate_totals(frm) { + let total_vat = 0; + let offset_vat = 0; + + // Считаем начисленный НДС + (frm.doc.table_qzfk || []).forEach(row => { + if (row.göstəricilər && row.göstəricilər.startsWith('301')) { + total_vat += (row.ədvməbləği || 0); + } + }); + + // Считаем зачетный НДС + (frm.doc.table_zvvh || []).forEach(row => { + if (row.göstəricilər && row.göstəricilər !== '317. Əməliyyatlar üzrə CƏMİ') { + offset_vat += (row.əlavədəyərvergisininməbləği || 0); + } + }); + + // Обновляем итоговую таблицу + let net_vat = total_vat - offset_vat; + + // Очищаем таблицу расчетов + frm.clear_table('table_kyya'); + + if (net_vat > 0) { + frm.add_child('table_kyya', { + göstəricilər: '326. Büdcəyə ödənilməlidir', + ədvməbləği: net_vat + }); + } else if (net_vat < 0) { + frm.add_child('table_kyya', { + göstəricilər: '327. Büdcədən qaytarılır', + ədvməbləği: Math.abs(net_vat) + }); + } + + frm.refresh_field('table_kyya'); +} + +// Горячие клавиши +frappe.ui.keys.add_shortcut({ + shortcut: 'ctrl+shift+f', + action: () => { + let frm = cur_frm; + if (frm && frm.doc.doctype === 'Declaration of value added tax') { + fill_declaration_automatically(frm); + } + }, + description: __('Avtomatik doldur') +}); \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.py b/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.py index fadeb10..c92ef25 100644 --- a/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.py +++ b/taxes_az/taxes_az/doctype/declaration_of_value_added_tax/declaration_of_value_added_tax.py @@ -1,9 +1,381 @@ -# Copyright (c) 2025, Jey Soft and contributors +# Copyright (c) 2025, Your Company and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document - +from frappe import _ +from frappe.utils import flt, getdate, add_months, cint +from datetime import datetime, date +import calendar class Declarationofvalueaddedtax(Document): pass + +import frappe +from frappe import _ +from datetime import datetime, date +from dateutil.relativedelta import relativedelta +import json + +@frappe.whitelist() +def fill_vat_declaration(declaration_name, company, quarter, year): + """ + Автоматическое заполнение НДС декларации для компании разработки ПО + """ + # Получаем документ декларации + doc = frappe.get_doc("Declaration of value added tax", declaration_name) + + # Определяем период (начало и конец квартала) + quarter_start, quarter_end = get_quarter_dates(quarter, year) + + # Основные данные компании + company_doc = frappe.get_doc("Company", company) + + # 1. ЗАПОЛНЯЕМ ОСНОВНУЮ ЧАСТЬ ДЕКЛАРАЦИИ + + # Получаем доходы от разработки ПО (Sales Invoice) + software_income = get_software_development_income(company, quarter_start, quarter_end) + + # Получаем входящий НДС (Purchase Invoice) + input_vat = get_input_vat(company, quarter_start, quarter_end) + + # Заполняем основную таблицу "Hesabat dövründəki dövriyyə üzrə" + fill_main_turnover_table(doc, software_income, input_vat) + + # 2. ЗАПОЛНЯЕМ ПРИЛОЖЕНИЕ 3 (Əlavə 3) + fill_appendix_3(doc, software_income, quarter_start, quarter_end) + + # 3. ЗАПОЛНЯЕМ ПРИЛОЖЕНИЕ 7 (если есть услуги от нерезидентов) + non_resident_services = get_non_resident_services(company, quarter_start, quarter_end) + if non_resident_services: + fill_appendix_7(doc, non_resident_services) + + # 4. ЗАПОЛНЯЕМ ПРИЛОЖЕНИЕ 9 (офисы) + fill_appendix_9(doc, company_doc) + + # Сохраняем документ + doc.save() + frappe.db.commit() + + return { + "status": "success", + "message": f"НДС декларация заполнена за {quarter} квартал {year} года", + "total_vat_payable": calculate_vat_payable(doc) + } + +def get_quarter_dates(quarter, year): + """Получаем даты начала и конца квартала""" + quarter_map = { + "1. Rüb": (1, 3), + "2. Rüb": (4, 6), + "3. Rüb": (7, 9), + "4. Rüb": (10, 12) + } + + start_month, end_month = quarter_map[quarter] + quarter_start = date(year, start_month, 1) + quarter_end = date(year, end_month, 1) + relativedelta(months=1) - relativedelta(days=1) + + return quarter_start, quarter_end + +def get_software_development_income(company, start_date, end_date): + """Получаем доходы от разработки ПО""" + # Выбираем все Sales Invoice за период + invoices = frappe.db.sql(""" + SELECT + si.name, + si.customer, + si.total, + si.total_taxes_and_charges, + si.grand_total, + si.posting_date, + si.is_pos, + si.mode_of_payment + FROM `tabSales Invoice` si + WHERE si.company = %s + AND si.posting_date BETWEEN %s AND %s + AND si.docstatus = 1 + AND si.is_return = 0 + """, (company, start_date, end_date), as_dict=True) + + # Группируем по типам операций + result = { + "total_without_vat": 0, + "total_vat": 0, + "cash_sales": 0, + "cashless_sales": 0, + "pos_sales": 0, + "invoices": invoices + } + + for inv in invoices: + # Сумма без НДС + base_amount = inv.total or 0 + vat_amount = inv.total_taxes_and_charges or 0 + + result["total_without_vat"] += base_amount + result["total_vat"] += vat_amount + + # Определяем тип оплаты + if inv.is_pos: + result["pos_sales"] += base_amount + elif inv.mode_of_payment and "cash" in inv.mode_of_payment.lower(): + result["cash_sales"] += base_amount + else: + result["cashless_sales"] += base_amount + + return result + +def get_input_vat(company, start_date, end_date): + """Получаем входящий НДС""" + purchases = frappe.db.sql(""" + SELECT + pi.name, + pi.supplier, + pi.total, + pi.total_taxes_and_charges, + pi.grand_total, + pi.posting_date, + s.supplier_type, + s.country + FROM `tabPurchase Invoice` pi + LEFT JOIN `tabSupplier` s ON pi.supplier = s.name + WHERE pi.company = %s + AND pi.posting_date BETWEEN %s AND %s + AND pi.docstatus = 1 + AND pi.is_return = 0 + """, (company, start_date, end_date), as_dict=True) + + result = { + "total_without_vat": 0, + "total_vat": 0, + "resident_purchases": 0, + "resident_vat": 0, + "non_resident_purchases": 0, + "non_resident_vat": 0, + "purchases": purchases + } + + for pur in purchases: + base_amount = pur.total or 0 + vat_amount = pur.total_taxes_and_charges or 0 + + result["total_without_vat"] += base_amount + result["total_vat"] += vat_amount + + # Определяем резидент или нет + if pur.country and pur.country != "Azerbaijan": + result["non_resident_purchases"] += base_amount + result["non_resident_vat"] += vat_amount + else: + result["resident_purchases"] += base_amount + result["resident_vat"] += vat_amount + + return result + +def fill_main_turnover_table(doc, income_data, input_vat_data): + """Заполняем основную таблицу по обороту""" + # Очищаем существующие строки + doc.table_qzfk = [] + + # 301 - ƏDV-nə 18 faiz dərəcə ilə cəlb olunan əməliyyatlar + doc.append("table_qzfk", { + "göstəricilər": "301. ƏDV-nə 18 faiz dərəcə ilə cəlb olunan əməliyyatlar", + "təqdimedilmişmalişvəxidmətlərindəyəriədvnəzərəalınmadan": 0, + "daxilolmuşməbləğədvnəzərəalınmadan": 0, + "ədvməbləği": 0 + }) + + # 301.1 - Malların təqdim edilməsi, işlərin görülməsi, xidmətlərin göstərilməsi + doc.append("table_qzfk", { + "göstəricilər": "301.1 Malların təqdim edilməsi, işlərin görülməsi, xidmətlərin göstərilməsi üzrə əməliyyatlar", + "təqdimedilmişmalişvəxidmətlərindəyəriədvnəzərəalınmadan": income_data["total_without_vat"], + "daxilolmuşməbləğədvnəzərəalınmadan": income_data["total_without_vat"], # Для услуг обычно равны + "ədvməbləği": income_data["total_vat"] + }) + + # 305 - Əməliyyatlar üzrə CƏMİ + total_turnover = income_data["total_without_vat"] + total_received = income_data["total_without_vat"] + total_vat = income_data["total_vat"] + + doc.append("table_qzfk", { + "göstəricilər": "305. Əməliyyatlar üzrə CƏMİ", + "təqdimedilmişmalişvəxidmətlərindəyəriədvnəzərəalınmadan": total_turnover, + "daxilolmuşməbləğədvnəzərəalınmadan": total_received, + "ədvməbləği": total_vat + }) + + # Заполняем таблицу Əvəzləşdirilən (зачеты) + doc.table_zvvh = [] + + # 308 - Электронные счета-фактуры + if input_vat_data["resident_vat"] > 0: + doc.append("table_zvvh", { + "göstəricilər": "308. VM 175.1-ci maddəsinə əsasən alınmış elektron qaimə-fakturalar (elektron vergi hesab-fakturalar) üzrə nağdsız qaydada ödənilmiş məbləğ", + "ödənilmişməbləğədvnəzərəalınmadan": input_vat_data["resident_purchases"], + "əlavədəyərvergisininməbləği": input_vat_data["resident_vat"] + }) + + # 312 - Платежи нерезидентам (если есть) + if input_vat_data["non_resident_vat"] > 0: + doc.append("table_zvvh", { + "göstəricilər": "312. Vergi Məcəlləsinin 169.4-cü maddəsinə əsasən qeyri-rezidentə ödənilmiş məbləğ", + "ödənilmişməbləğədvnəzərəalınmadan": input_vat_data["non_resident_purchases"], + "əlavədəyərvergisininməbləği": input_vat_data["non_resident_vat"] + }) + + # 317 - Итого по зачетам + doc.append("table_zvvh", { + "göstəricilər": "317. Əməliyyatlar üzrə CƏMİ", + "ödənilmişməbləğədvnəzərəalınmadan": input_vat_data["total_without_vat"], + "əlavədəyərvergisininməbləği": input_vat_data["total_vat"] + }) + + # Заполняем Hesablaşmalar (расчеты с бюджетом) + doc.table_kyya = [] + + vat_payable = total_vat - input_vat_data["total_vat"] + + if vat_payable > 0: + # 326 - К уплате в бюджет + doc.append("table_kyya", { + "göstəricilər": "326. Büdcəyə ödənilməlidir", + "ədvməbləği": vat_payable + }) + else: + # 327 - К возврату из бюджета + doc.append("table_kyya", { + "göstəricilər": "327. Büdcədən qaytarılır", + "ədvməbləği": abs(vat_payable) + }) + +def fill_appendix_3(doc, income_data, start_date, end_date): + """Заполняем Приложение 3 - детализация операций""" + # Таблица 1 - ƏDV tutulan dövriyyə + doc.table_kdll = [] + + # 301.1.2.6 - Прочие услуги (разработка ПО) + doc.append("table_kdll", { + "göstəricilər": "301.1.2.6 Sair işlərin görülməsi və xidmətlərin göstərilməsi üzrə əməliyyatlar", + "təqdimedilmişmalişvəxidmətlərindəyəriədvnəzərəalınmadan": income_data["total_without_vat"], + "daxilolmuşməbləğədvnəzərəalınmadan": income_data["total_without_vat"] + }) + +def fill_appendix_7(doc, non_resident_services): + """Заполняем Приложение 7 - услуги нерезидентов""" + doc.table_vonn = [] + + for service in non_resident_services: + doc.append("table_vonn", { + "qeyrirezidentinadı": service["supplier_name"], + "rezidentolduğuölkəninadı": service["country"], + "görülmüşişinxidmətinadı": service["service_description"], + "görülmüşişindövrü": service["service_period"], + "rezidentolduğuölkədəkivöeni": service["foreign_tax_id"], + "büdcəyəödənişitəsdiqtəsdiqedənsənədinnsi": service["payment_doc_no"], + "sənədintarixi": service["payment_date"], + "ödənilmişümumiədvsizməbləği": service["amount_without_vat"], + "ödənilmişümumiədvməbləği": service["vat_amount"] + }) + +def fill_appendix_9(doc, company_doc): + """Заполняем Приложение 9 - данные об офисах""" + doc.table_gpfh = [] + + # Получаем офисы из business_objects компании + if company_doc.business_objects: + for idx, obj in enumerate(company_doc.business_objects): + doc.append("table_gpfh", { + "sətrinkodu": idx + 1, + "obyektinadı": obj.object_name, + "obyektinkodu": obj.object_code, + "obyektinünvanı": obj.address, + "obyektinəsassahəsikvm": obj.main_area, + "obyektinəlavəsahəsikvm": obj.additional_area, + "obyektinfəaliyyətsahəsiüzrətəsnifatı": "İXTİSASLI, ELMİ VƏ TEXNİKİ FƏALİYYƏT", + "obyektinəsasfəaliyyətnövününkoduvəadı": "J62010 - Kompüter proqramlaşdırma fəaliyyəti", + "obyektüzrəişçilərincəmisayı": obj.total_employees, + "ocümlədənobyektüzrəmüvəqqətiişçilərinsayı": obj.temporary_employees, + "obyektüzrətəqdimedilmişmallarınişlərinxidmətlərindəyərimanatla": obj.turnover, + "obyekttəqdimedilmişmallaraəldəedilmişhasilatməbləği": obj.revenue + }) + +def get_non_resident_services(company, start_date, end_date): + """Получаем услуги от нерезидентов""" + # Здесь нужно определить логику получения услуг от нерезидентов + # Например, из Purchase Invoice где поставщик - нерезидент + + services = frappe.db.sql(""" + SELECT + pi.name, + pi.supplier, + s.supplier_name, + s.country, + pi.total as amount_without_vat, + pi.total_taxes_and_charges as vat_amount, + pi.posting_date, + pi.bill_no + FROM `tabPurchase Invoice` pi + JOIN `tabSupplier` s ON pi.supplier = s.name + WHERE pi.company = %s + AND pi.posting_date BETWEEN %s AND %s + AND pi.docstatus = 1 + AND s.country != 'Azerbaijan' + AND s.country IS NOT NULL + """, (company, start_date, end_date), as_dict=True) + + result = [] + for service in services: + result.append({ + "supplier_name": service.supplier_name, + "country": service.country, + "service_description": "IT услуги", # Можно брать из описания в PI + "service_period": service.posting_date.strftime("%Y%m"), + "foreign_tax_id": "", # Нужно добавить поле в Supplier + "payment_doc_no": service.bill_no or service.name, + "payment_date": service.posting_date.strftime("%Y%m%d"), + "amount_without_vat": service.amount_without_vat, + "vat_amount": service.vat_amount or (service.amount_without_vat * 0.18) + }) + + return result + +def calculate_vat_payable(doc): + """Рассчитываем НДС к уплате""" + # Берем из таблицы Hesablaşmalar + for row in doc.table_kyya: + if row.göstəricilər == "326. Büdcəyə ödənilməlidir": + return row.ədvməbləği + elif row.göstəricilər == "327. Büdcədən qaytarılır": + return -row.ədvməbləği + return 0 + +# Функция для вызова из UI +@frappe.whitelist() +def create_and_fill_vat_declaration(company, quarter, year): + """Создать и заполнить НДС декларацию""" + # Создаем новый документ + doc = frappe.new_doc("Declaration of value added tax") + + # Базовые поля + doc.vergidövrü = "Rüblük" + doc.rüb = quarter + doc.il = int(year) + + # Данные компании + company_doc = frappe.get_doc("Company", company) + doc.vöen = company_doc.tax_id + doc.adı = company_doc.company_name + doc.vergiorqanı = company_doc.tax_authority or "Dövlət Vergi xidmətinin Bakı şəhəri Kiçik Sahibkarlıqla İş üzrə Baş idarəsi" + + # Сохраняем документ + doc.insert() + + # Заполняем данными + result = fill_vat_declaration(doc.name, company, quarter, year) + + return { + "declaration_name": doc.name, + "result": result + } \ No newline at end of file