Prepared for VAT form filling

This commit is contained in:
Ali 2025-06-20 13:58:45 +04:00
parent cc48775100
commit 22ff793727
19 changed files with 2182 additions and 689 deletions

View File

@ -553,22 +553,42 @@ window.ETaxesCompany = {
/** /**
* Parse profile data and extract field mappings * Parse profile data and extract field mappings
* ПОЛНАЯ ВЕРСИЯ: Включает все поля включая VAT и новые поля
*/ */
_parseProfileData: function(profile, frm) { _parseProfileData: function(profile, frm) {
const updates = []; const updates = [];
// Helper function to add update if value is different // Helper function to add update if value is different
function addUpdate(fieldname, newValue, label, oldValue) { function addUpdate(fieldname, newValue, label, oldValue) {
if (newValue && newValue !== oldValue && newValue.trim() !== '') { // Специальная обработка для 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({ updates.push({
fieldname: fieldname, fieldname: fieldname,
newValue: newValue.trim(), 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 || '', oldValue: oldValue || '',
label: label label: label
}); });
} }
} }
}
}
// Helper function to format date from YYYYMMDD to YYYY-MM-DD // Helper function to format date from YYYYMMDD to YYYY-MM-DD
function formatDate(dateStr) { function formatDate(dateStr) {
@ -579,6 +599,7 @@ window.ETaxesCompany = {
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
} }
// BASIC COMPANY INFORMATION
// Tax ID mapping (TIN) // Tax ID mapping (TIN)
if (profile.tin) { if (profile.tin) {
addUpdate('tax_id', profile.tin, __('Tax ID'), frm.doc.tax_id); addUpdate('tax_id', profile.tin, __('Tax ID'), frm.doc.tax_id);
@ -589,10 +610,22 @@ window.ETaxesCompany = {
addUpdate('registration_details', profile.stateRegistrationDocumentNumber, __('Registration Details'), frm.doc.registration_details); addUpdate('registration_details', profile.stateRegistrationDocumentNumber, __('Registration Details'), frm.doc.registration_details);
} }
// Phone mapping // Phone mapping - prefer mobile over landline
const phoneNumber = ETaxesCompany._extractPhoneNumber(profile); const mobilePhone = profile.mobilePhoneNumber || ETaxesCompany._extractPhoneNumber(profile, 'mobile');
if (phoneNumber) { const landlinePhone = profile.landlinePhoneNumber || ETaxesCompany._extractPhoneNumber(profile, 'landline');
addUpdate('phone_no', phoneNumber, __('Phone'), frm.doc.phone_no);
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 // Email mapping
@ -606,19 +639,41 @@ window.ETaxesCompany = {
addUpdate('date_of_establishment', regDate, __('Date of Establishment'), frm.doc.date_of_establishment); 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) // Domain mapping (if available)
if (profile.mainActivity) { if (profile.mainActivity) {
addUpdate('domain', profile.mainActivity, __('Domain'), frm.doc.domain); 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);
}
// Main Activity mapping (custom field) // Director PIN
if (profile.pin) {
addUpdate('director_pin', profile.pin, __('Director PIN'), frm.doc.director_pin);
}
// BUSINESS ACTIVITIES
// Main Activity mapping
if (profile.mainActivity) { if (profile.mainActivity) {
addUpdate('main_activity', profile.mainActivity, __('Main Activity'), frm.doc.main_activity); addUpdate('main_activity', profile.mainActivity, __('Main Activity'), frm.doc.main_activity);
} }
// Taxation System mapping (custom field) // 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) { if (profile.taxRate) {
let taxationSystem = ''; let taxationSystem = '';
if (profile.taxRate === 'ƏDV' || profile.taxRate === 'VAT' || profile.taxRate === '18%') { if (profile.taxRate === 'ƏDV' || profile.taxRate === 'VAT' || profile.taxRate === '18%') {
@ -626,16 +681,15 @@ window.ETaxesCompany = {
} else if (profile.taxRate === '0%' || profile.taxRate === 'ƏDV 0%') { } else if (profile.taxRate === '0%' || profile.taxRate === 'ƏDV 0%') {
taxationSystem = '0% Value-added tax'; taxationSystem = '0% Value-added tax';
} else { } else {
taxationSystem = profile.taxRate; // Если не распознали, используем как есть taxationSystem = profile.taxRate;
} }
addUpdate('taxation_system', taxationSystem, __('Taxation System'), frm.doc.taxation_system); addUpdate('taxation_system', taxationSystem, __('Taxation System'), frm.doc.taxation_system);
} }
// Business Classification mapping (custom field) // Business Classification mapping
if (profile.criteriaOfBusinessEntity) { if (profile.criteriaOfBusinessEntity) {
let businessClassification = ''; let businessClassification = '';
// Преобразование значений E-Taxes в правильные названия
switch (profile.criteriaOfBusinessEntity.toLowerCase()) { switch (profile.criteriaOfBusinessEntity.toLowerCase()) {
case 'micro': case 'micro':
businessClassification = 'Micro Entrepreneur'; businessClassification = 'Micro Entrepreneur';
@ -652,15 +706,12 @@ window.ETaxesCompany = {
businessClassification = 'Big Entrepreneur'; businessClassification = 'Big Entrepreneur';
break; break;
default: default:
// Если не распознали, используем исходное значение с добавлением " Entrepreneur"
businessClassification = profile.criteriaOfBusinessEntity + ' Entrepreneur'; businessClassification = profile.criteriaOfBusinessEntity + ' Entrepreneur';
} }
addUpdate('business_classification', businessClassification, __('Business Classification'), frm.doc.business_classification); addUpdate('business_classification', businessClassification, __('Business Classification'), frm.doc.business_classification);
} }
// НОВЫЕ ПОЛЯ - добавляем пока коды, названия будут обновлены асинхронно
// State Registration Authority // State Registration Authority
if (profile.stateRegistrationName) { if (profile.stateRegistrationName) {
addUpdate('state_registration_authority', profile.stateRegistrationName, __('State Registration Authority'), frm.doc.state_registration_authority); addUpdate('state_registration_authority', profile.stateRegistrationName, __('State Registration Authority'), frm.doc.state_registration_authority);
@ -671,9 +722,49 @@ window.ETaxesCompany = {
addUpdate('taxpayer_activity_group', profile.activityGroup, __('Taxpayer Activity Group'), frm.doc.taxpayer_activity_group); addUpdate('taxpayer_activity_group', profile.activityGroup, __('Taxpayer Activity Group'), frm.doc.taxpayer_activity_group);
} }
// VAT ПОЛЯ // 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') { if (profile.vatInfo && typeof profile.vatInfo === 'object') {
// VAT Registration Date (prOperationTableOperationDate) // VAT Registration Date
if (profile.vatInfo.prOperationTableOperationDate) { if (profile.vatInfo.prOperationTableOperationDate) {
const vatRegDate = formatDate(profile.vatInfo.prOperationTableOperationDate); const vatRegDate = formatDate(profile.vatInfo.prOperationTableOperationDate);
if (vatRegDate) { if (vatRegDate) {
@ -681,12 +772,12 @@ window.ETaxesCompany = {
} }
} }
// VAT Certificate Number (recDocumentDocumentNumber) // VAT Certificate Number
if (profile.vatInfo.recDocumentDocumentNumber) { if (profile.vatInfo.recDocumentDocumentNumber) {
addUpdate('vat_certificate_number', profile.vatInfo.recDocumentDocumentNumber, __('VAT Certificate Number'), frm.doc.vat_certificate_number); addUpdate('vat_certificate_number', profile.vatInfo.recDocumentDocumentNumber, __('VAT Certificate Number'), frm.doc.vat_certificate_number);
} }
// VAT Certificate Date (prVatOperationsRegDate) // VAT Certificate Date
if (profile.vatInfo.prVatOperationsRegDate) { if (profile.vatInfo.prVatOperationsRegDate) {
const vatCertDate = formatDate(profile.vatInfo.prVatOperationsRegDate); const vatCertDate = formatDate(profile.vatInfo.prVatOperationsRegDate);
if (vatCertDate) { if (vatCertDate) {
@ -695,15 +786,95 @@ window.ETaxesCompany = {
} }
} }
// ОРГАНИЗАЦИОННАЯ СТРУКТУРА // ORGANIZATIONAL STRUCTURE
// Parent Organization
// Parent Organization (из headOrganizationName)
if (profile.headOrganizationName && Array.isArray(profile.headOrganizationName) && profile.headOrganizationName.length > 0) { if (profile.headOrganizationName && Array.isArray(profile.headOrganizationName) && profile.headOrganizationName.length > 0) {
// Берем первую головную организацию или объединяем через запятую
const parentOrgText = profile.headOrganizationName.join(', '); const parentOrgText = profile.headOrganizationName.join(', ');
addUpdate('parent_organization', parentOrgText, __('Parent Organization'), frm.doc.parent_organization); 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; return updates;
}, },
@ -726,24 +897,28 @@ window.ETaxesCompany = {
} }
}, },
/**
* ИСПРАВЛЕННАЯ ВЕРСИЯ: Обновляем Additional Activity Types из additionalActivityCodes
* с проверкой существования в Main type of activity
*/
_updateAdditionalActivities: function(profile, frm) { _updateAdditionalActivities: function(profile, frm) {
if (profile.additionalActivityCodes && Array.isArray(profile.additionalActivityCodes) && profile.additionalActivityCodes.length > 0) { if (profile.additionalActivityCodes && Array.isArray(profile.additionalActivityCodes) && profile.additionalActivityCodes.length > 0) {
// Очищаем существующие записи в табличной части Additional Activity Types // ВАЖНО: Не очищаем таблицу сразу, а работаем с существующими данными
frm.clear_table('additional_activity_types'); const existingActivities = frm.doc.additional_activity_types || [];
const existingCodes = existingActivities.map(row => row.activity_type);
let addedCount = 0; let addedCount = 0;
let skippedCount = 0; let skippedCount = 0;
let processedCount = 0; let duplicateCount = 0;
// Функция для проверки и добавления одного кода активности // Функция для проверки и добавления одного кода активности
function processActivityCode(activityCode, callback) { function processActivityCode(activityCode, callback) {
if (activityCode && activityCode.trim()) { if (activityCode && activityCode.trim()) {
const trimmedCode = activityCode.trim(); const trimmedCode = activityCode.trim();
// Проверяем, не добавлен ли уже этот код
if (existingCodes.includes(trimmedCode)) {
duplicateCount++;
if (callback) callback();
return;
}
// Проверяем существование записи Main type of activity перед добавлением // Проверяем существование записи Main type of activity перед добавлением
frappe.call({ frappe.call({
method: 'frappe.client.get_value', method: 'frappe.client.get_value',
@ -753,8 +928,6 @@ window.ETaxesCompany = {
filters: {name: trimmedCode} filters: {name: trimmedCode}
}, },
callback: function(r) { callback: function(r) {
processedCount++;
if (r.message && r.message.name) { if (r.message && r.message.name) {
// Запись существует - добавляем // Запись существует - добавляем
let row = frm.add_child('additional_activity_types'); let row = frm.add_child('additional_activity_types');
@ -765,17 +938,14 @@ window.ETaxesCompany = {
skippedCount++; skippedCount++;
} }
// Вызываем callback когда обработка завершена
if (callback) callback(); if (callback) callback();
}, },
error: function() { error: function() {
processedCount++;
skippedCount++; skippedCount++;
if (callback) callback(); if (callback) callback();
} }
}); });
} else { } else {
processedCount++;
skippedCount++; skippedCount++;
if (callback) callback(); if (callback) callback();
} }
@ -784,7 +954,7 @@ window.ETaxesCompany = {
// Функция для последовательной обработки всех кодов // Функция для последовательной обработки всех кодов
function processAllCodes(codes, index = 0) { function processAllCodes(codes, index = 0) {
if (index >= codes.length) { if (index >= codes.length) {
// Все коды обработаны - обновляем UI и показываем результат // Все коды обработаны - обновляем UI
frm.refresh_field('additional_activity_types'); frm.refresh_field('additional_activity_types');
let message = ''; let message = '';
@ -793,10 +963,19 @@ window.ETaxesCompany = {
if (skippedCount > 0) { if (skippedCount > 0) {
message += __(', skipped {0} (not found)', [skippedCount]); message += __(', skipped {0} (not found)', [skippedCount]);
} }
if (duplicateCount > 0) {
message += __(', {0} already existed', [duplicateCount]);
}
frappe.show_alert({ frappe.show_alert({
message: message, message: message,
indicator: 'green' indicator: 'green'
}, 4); }, 4);
} else if (duplicateCount > 0) {
message = __('All {0} activities already exist', [duplicateCount]);
frappe.show_alert({
message: message,
indicator: 'blue'
}, 4);
} else { } else {
message = __('No valid additional activities found'); message = __('No valid additional activities found');
if (skippedCount > 0) { if (skippedCount > 0) {
@ -828,139 +1007,198 @@ window.ETaxesCompany = {
} }
}, },
/**
* ОБНОВЛЕННАЯ ФУНКЦИЯ: Обновляем дочерние организации из affiliatesNames
*/
_updateAffiliateOrganizations: function(profile, frm) { _updateAffiliateOrganizations: function(profile, frm) {
if (profile.affiliatesNames && Array.isArray(profile.affiliatesNames) && profile.affiliatesNames.length > 0) { // Проверяем наличие дочерних организаций
// Очищаем существующие записи 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'); frm.clear_table('affiliate_organizations');
let addedCount = 0; let addedCount = 0;
let skippedCount = 0;
// Добавляем каждую дочернюю организацию из affiliatesNames // Определяем максимальную длину массивов
profile.affiliatesNames.forEach(function(orgName, index) { const maxLength = Math.max(
if (orgName && orgName.trim()) { 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'); let row = frm.add_child('affiliate_organizations');
// Имя заполняем из массива affiliatesNames // Заполняем имя организации
row.organization_name = orgName.trim(); if (orgName) {
row.organization_name = orgName;
} else {
// Если нет имени, но есть TIN, используем TIN как имя
row.organization_name = `Organization (TIN: ${orgTin})`;
}
// Заполняем TIN если есть
if (orgTin) {
row.organization_tin = orgTin;
}
addedCount++; 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'); frm.refresh_field('affiliate_organizations');
// Показываем результат
if (addedCount > 0) { if (addedCount > 0) {
let message = __('Added {0} affiliate organization(s)', [addedCount]);
if (skippedCount > 0) {
message += __(', skipped {0} empty record(s)', [skippedCount]);
}
frappe.show_alert({ frappe.show_alert({
message: __('Added {0} affiliate organizations', [addedCount]), message: message,
indicator: 'green' 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); }, 3);
} }
} else { } else {
// Очищаем таблицу если нет дочерних организаций // Нет дочерних организаций - очищаем таблицу
if (frm.doc.affiliate_organizations && frm.doc.affiliate_organizations.length > 0) {
frm.clear_table('affiliate_organizations'); frm.clear_table('affiliate_organizations');
frm.refresh_field('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) { updateCompanyFromProfile: function(profileData, frm) {
try { try {
const profile = profileData.profile || profileData; const profile = profileData.profile || profileData;
const updates = ETaxesCompany._parseProfileData(profile, frm); const updates = ETaxesCompany._parseProfileData(profile, frm);
if (updates.length > 0) {
// Show confirmation with list of changes
let changesList = '<ul style="text-align: left; margin: 10px 0;">';
updates.forEach(update => {
changesList += `<li><strong>${update.label}:</strong> ${update.oldValue || __('(empty)')}${update.newValue}</li>`;
});
changesList += '</ul>';
// Добавляем информацию о дополнительных данных
let additionalInfo = '';
if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) {
additionalInfo += `<br><strong>${__('Additional activities to be added')}:</strong> ${profile.additionalActivityCodes.length} ${__('activities')}`;
}
if (profile.affiliatesNames && profile.affiliatesNames.length > 0) {
additionalInfo += `<br><strong>${__('Affiliate organizations to be added')}:</strong> ${profile.affiliatesNames.length} ${__('organizations')}`;
}
frappe.confirm(
__('The following company fields will be updated with data from E-Taxes:') +
'<br><br>' + changesList + additionalInfo +
'<br>' + __('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 hasAdditionalActivities = profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0;
const hasAffiliates = profile.affiliatesNames && profile.affiliatesNames.length > 0; const hasAffiliates = profile.affiliatesNames && profile.affiliatesNames.length > 0;
const hasDictionaryFields = profile.taxAuthorityCode || profile.propertyType; const hasDictionaryFields = profile.taxAuthorityCode || profile.propertyType;
if (hasAdditionalActivities || hasAffiliates || hasDictionaryFields) { // Если есть обновления полей или дополнительные данные
let updateInfo = __('The following data will be updated:'); if (updates.length > 0 || hasAdditionalActivities || hasAffiliates || hasDictionaryFields) {
if (hasAdditionalActivities) { // Формируем сообщение для подтверждения
updateInfo += `<br><strong>${__('Additional activities')}:</strong> ${profile.additionalActivityCodes.length} ${__('activities')}`; let confirmMessage = '';
}
if (hasAffiliates) { if (updates.length > 0) {
updateInfo += `<br><strong>${__('Affiliate organizations')}:</strong> ${profile.affiliatesNames.length} ${__('organizations')}`; confirmMessage = __('The following company fields will be updated with data from E-Taxes:') + '<br><br>';
} let changesList = '<ul style="text-align: left; margin: 10px 0;">';
if (hasDictionaryFields) { updates.forEach(update => {
updateInfo += `<br><strong>${__('Dictionary fields')}:</strong> Tax Authority, Property Type`; changesList += `<li><strong>${update.label}:</strong> ${update.oldValue || __('(empty)')}${update.newValue}</li>`;
});
changesList += '</ul>';
confirmMessage += changesList;
} }
frappe.confirm( // Добавляем информацию о дополнительных данных
updateInfo + '<br><br>' + __('Continue?'), let additionalInfo = '';
function() {
if (hasAdditionalActivities) { if (hasAdditionalActivities) {
ETaxesCompany._updateAdditionalActivities(profile, frm); additionalInfo += `<br><strong>${__('Additional activities to be added')}:</strong> ${profile.additionalActivityCodes.length} ${__('activities')}`;
} }
if (hasAffiliates) { if (hasAffiliates) {
ETaxesCompany._updateAffiliateOrganizations(profile, frm); additionalInfo += `<br><strong>${__('Affiliate organizations to be added')}:</strong> ${profile.affiliatesNames.length} ${__('organizations')}`;
} }
if (!confirmMessage && additionalInfo) {
confirmMessage = __('The following data will be updated:');
}
confirmMessage += additionalInfo;
confirmMessage += '<br>' + __('Continue?');
frappe.confirm(
confirmMessage,
function() {
// ВАЖНО: Отключаем автосохранение перед массовыми изменениями
frm.disable_save();
// Apply all field updates
updates.forEach(update => {
frm.set_value(update.fieldname, update.newValue);
});
// Обновляем поля из справочников
if (hasDictionaryFields) { if (hasDictionaryFields) {
ETaxesCompany._updateDictionaryFields(profile, frm); ETaxesCompany._updateDictionaryFields(profile, frm);
} }
// Автосохранение после добавления данных // Задержка перед обновлением табличных частей
setTimeout(function() { setTimeout(function() {
frm.save(); // Обновляем дополнительные активности
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); }, 500);
}, 300);
}, },
function() { function() {
frappe.show_alert({ frappe.show_alert({
@ -975,7 +1213,6 @@ window.ETaxesCompany = {
indicator: 'blue' indicator: 'blue'
}, 3); }, 3);
} }
}
} catch (e) { } catch (e) {
console.error('Error parsing profile data:', e); console.error('Error parsing profile data:', e);
@ -989,7 +1226,15 @@ window.ETaxesCompany = {
/** /**
* Extract best phone number from profile * Extract best phone number from profile
*/ */
_extractPhoneNumber: function(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 // Priority: mobile > landline > contacts mobile > contacts home
if (profile.mobilePhoneNumber) { if (profile.mobilePhoneNumber) {
return profile.mobilePhoneNumber; return profile.mobilePhoneNumber;
@ -1068,35 +1313,63 @@ window.ETaxesCompany = {
const regDate = moment(profile.taxpayerRegistrationDate).format('DD.MM.YYYY'); const regDate = moment(profile.taxpayerRegistrationDate).format('DD.MM.YYYY');
html += `<tr><td><strong>${__('Registration Date')}</strong></td><td class="text-content">${regDate}</td></tr>`; html += `<tr><td><strong>${__('Registration Date')}</strong></td><td class="text-content">${regDate}</td></tr>`;
} }
if (profile.stateRegistrationDocumentIssuedDate) {
const issuedDate = moment(profile.stateRegistrationDocumentIssuedDate).format('DD.MM.YYYY');
html += `<tr><td><strong>${__('State Registration Document Issued Date')}</strong></td><td class="text-content">${issuedDate}</td></tr>`;
}
if (profile.taxpayerStatus) { if (profile.taxpayerStatus) {
html += `<tr><td><strong>${__('Status')}</strong></td><td class="text-content">${profile.taxpayerStatus}</td></tr>`; const statusText = profile.taxpayerStatus === 'A' ? __('Active') : profile.taxpayerStatus;
html += `<tr><td><strong>${__('Status')}</strong></td><td class="text-content">${statusText}</td></tr>`;
}
if (profile.legalFormCode) {
html += `<tr><td><strong>${__('Legal Form Code')}</strong></td><td class="text-content">${profile.legalFormCode}</td></tr>`;
}
if (profile.tinType) {
html += `<tr><td><strong>${__('TIN Type')}</strong></td><td class="text-content">${profile.tinType}</td></tr>`;
} }
html += '</table></div>'; html += '</table></div>';
// Contact Information // Contact Information
const phoneNumber = ETaxesCompany._extractPhoneNumber(profile);
const address = profile.legalAddress || profile.actualAddress;
if (phoneNumber || profile.email || address) {
html += '<div class="section"><h5>' + __('Contact Information') + '</h5>'; html += '<div class="section"><h5>' + __('Contact Information') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">'; html += '<table class="table table-bordered table-sm profile-table">';
if (phoneNumber) { if (profile.mobilePhoneNumber) {
html += `<tr><td><strong>${__('Phone')}</strong></td><td class="text-content">${phoneNumber}</td></tr>`; html += `<tr><td><strong>${__('Mobile Phone')}</strong></td><td class="text-content">${profile.mobilePhoneNumber}</td></tr>`;
}
if (profile.landlinePhoneNumber) {
html += `<tr><td><strong>${__('Landline Phone')}</strong></td><td class="text-content">${profile.landlinePhoneNumber}</td></tr>`;
} }
if (profile.email) { if (profile.email) {
html += `<tr><td><strong>${__('Email')}</strong></td><td class="text-content">${profile.email}</td></tr>`; html += `<tr><td><strong>${__('Email')}</strong></td><td class="text-content">${profile.email}</td></tr>`;
} }
if (address) { if (profile.legalAddress) {
html += `<tr><td><strong>${__('Address')}</strong></td><td class="text-content">${address}</td></tr>`; html += `<tr><td><strong>${__('Legal Address')}</strong></td><td class="text-content">${profile.legalAddress}</td></tr>`;
}
if (profile.actualAddress) {
html += `<tr><td><strong>${__('Actual Address')}</strong></td><td class="text-content">${profile.actualAddress}</td></tr>`;
}
if (profile.addressForMail) {
html += `<tr><td><strong>${__('Address for Mail')}</strong></td><td class="text-content">${profile.addressForMail}</td></tr>`;
}
// 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 += `<tr><td colspan="2"><strong>${__('Legal Address Details')}</strong></td></tr>`;
if (addr.postcode) html += `<tr><td>${__('Postcode')}</td><td class="text-content">${addr.postcode}</td></tr>`;
if (addr.region) html += `<tr><td>${__('Region')}</td><td class="text-content">${addr.region}</td></tr>`;
if (addr.locality) html += `<tr><td>${__('Locality')}</td><td class="text-content">${addr.locality}</td></tr>`;
if (addr.street) html += `<tr><td>${__('Street')}</td><td class="text-content">${addr.street}</td></tr>`;
if (addr.houseNumber) html += `<tr><td>${__('House Number')}</td><td class="text-content">${addr.houseNumber}</td></tr>`;
if (addr.roomNumber) html += `<tr><td>${__('Room Number')}</td><td class="text-content">${addr.roomNumber}</td></tr>`;
}
} }
html += '</table></div>'; html += '</table></div>';
}
// Business Information // Business Information
if (profile.mainActivity || profile.additionalActivityCodes || profile.legalFormCode || profile.criteriaOfBusinessEntity) {
html += '<div class="section"><h5>' + __('Business Information') + '</h5>'; html += '<div class="section"><h5>' + __('Business Information') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">'; html += '<table class="table table-bordered table-sm profile-table">';
@ -1106,18 +1379,22 @@ window.ETaxesCompany = {
if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) { if (profile.additionalActivityCodes && profile.additionalActivityCodes.length > 0) {
html += `<tr><td><strong>${__('Additional Activity Codes')}</strong></td><td class="text-content">${profile.additionalActivityCodes.join(', ')}</td></tr>`; html += `<tr><td><strong>${__('Additional Activity Codes')}</strong></td><td class="text-content">${profile.additionalActivityCodes.join(', ')}</td></tr>`;
} }
if (profile.legalFormCode) {
html += `<tr><td><strong>${__('Legal Form Code')}</strong></td><td class="text-content">${profile.legalFormCode}</td></tr>`;
}
if (profile.criteriaOfBusinessEntity) { if (profile.criteriaOfBusinessEntity) {
html += `<tr><td><strong>${__('Business Entity Criteria')}</strong></td><td class="text-content">${profile.criteriaOfBusinessEntity}</td></tr>`; html += `<tr><td><strong>${__('Business Entity Criteria')}</strong></td><td class="text-content">${profile.criteriaOfBusinessEntity}</td></tr>`;
} }
if (profile.activityGroup) {
html += '</table></div>'; html += `<tr><td><strong>${__('Activity Group')}</strong></td><td class="text-content">${profile.activityGroup}</td></tr>`;
}
if (profile.sportBettingOperator !== undefined) {
html += `<tr><td><strong>${__('Sport Betting Operator')}</strong></td><td class="text-content">${profile.sportBettingOperator ? __('Yes') : __('No')}</td></tr>`;
}
if (profile.hasActiveProductionObject !== undefined) {
html += `<tr><td><strong>${__('Has Active Production Object')}</strong></td><td class="text-content">${profile.hasActiveProductionObject ? __('Yes') : __('No')}</td></tr>`;
} }
html += '</table></div>';
// Tax Information // Tax Information
if (profile.taxRate || profile.vatInfo || profile.specialTaxRegime !== undefined || profile.taxAuthorityCode || profile.propertyType) {
html += '<div class="section"><h5>' + __('Tax Information') + '</h5>'; html += '<div class="section"><h5>' + __('Tax Information') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">'; html += '<table class="table table-bordered table-sm profile-table">';
@ -1133,29 +1410,47 @@ window.ETaxesCompany = {
if (profile.propertyType) { if (profile.propertyType) {
html += `<tr><td><strong>${__('Property Type')}</strong></td><td class="text-content">${profile.propertyType}</td></tr>`; html += `<tr><td><strong>${__('Property Type')}</strong></td><td class="text-content">${profile.propertyType}</td></tr>`;
} }
if (profile.taxSystemType) {
html += `<tr><td><strong>${__('Tax System Type')}</strong></td><td class="text-content">${profile.taxSystemType}</td></tr>`;
}
if (profile.taxSystems && profile.taxSystems.length > 0) {
html += `<tr><td><strong>${__('Tax Systems')}</strong></td><td class="text-content">${profile.taxSystems.join(', ')}</td></tr>`;
}
if (profile.isRisky !== undefined) {
html += `<tr><td><strong>${__('Is Risky Taxpayer')}</strong></td><td class="text-content">${profile.isRisky ? __('Yes') : __('No')}</td></tr>`;
}
if (profile.isTaxPayerInCancellationProcess !== undefined) {
html += `<tr><td><strong>${__('In Cancellation Process')}</strong></td><td class="text-content">${profile.isTaxPayerInCancellationProcess ? __('Yes') : __('No')}</td></tr>`;
}
if (profile.vatInfo) { if (profile.vatInfo) {
const formattedVat = ETaxesCompany._formatVatInfo(profile.vatInfo); const formattedVat = ETaxesCompany._formatVatInfo(profile.vatInfo);
html += `<tr><td><strong>${__('VAT Info')}</strong></td><td class="text-content">${formattedVat}</td></tr>`; html += `<tr><td><strong>${__('VAT Info')}</strong></td><td class="text-content">${formattedVat}</td></tr>`;
} }
html += '</table></div>'; html += '</table></div>';
}
// Organizational Structure // Organizational Structure
if (profile.affiliatesNames) {
html += '<div class="section"><h5>' + __('Organizational Structure') + '</h5>'; html += '<div class="section"><h5>' + __('Organizational Structure') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">'; html += '<table class="table table-bordered table-sm profile-table">';
if (profile.headOrganizationName && profile.headOrganizationName.length > 0) {
html += `<tr><td><strong>${__('Parent Organization')}</strong></td><td class="text-content">${profile.headOrganizationName.join(', ')}</td></tr>`;
}
if (profile.headOrganizationTin) {
html += `<tr><td><strong>${__('Parent Organization TIN')}</strong></td><td class="text-content">${profile.headOrganizationTin}</td></tr>`;
}
if (profile.affiliatesNames && profile.affiliatesNames.length > 0) { if (profile.affiliatesNames && profile.affiliatesNames.length > 0) {
const affiliatesText = profile.affiliatesNames.join(', '); const affiliatesText = profile.affiliatesNames.join(', ');
html += `<tr><td><strong>${__('Affiliate Organizations')}</strong></td><td class="text-content">${affiliatesText}</td></tr>`; html += `<tr><td><strong>${__('Affiliate Organizations')}</strong></td><td class="text-content">${affiliatesText}</td></tr>`;
} }
if (profile.affiliatesTins && profile.affiliatesTins.length > 0) {
html += '</table></div>'; const affiliatesTinsText = profile.affiliatesTins.join(', ');
html += `<tr><td><strong>${__('Affiliate Organizations TINs')}</strong></td><td class="text-content">${affiliatesTinsText}</td></tr>`;
} }
// Director Information html += '</table></div>';
if (profile.companyDirectorName || profile.chief) {
// Management Information
html += '<div class="section"><h5>' + __('Management Information') + '</h5>'; html += '<div class="section"><h5>' + __('Management Information') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">'; html += '<table class="table table-bordered table-sm profile-table">';
@ -1171,11 +1466,72 @@ window.ETaxesCompany = {
html += `<tr><td><strong>${__('Chief PIN')}</strong></td><td class="text-content">${profile.chief.pin}</td></tr>`; html += `<tr><td><strong>${__('Chief PIN')}</strong></td><td class="text-content">${profile.chief.pin}</td></tr>`;
} }
} }
if (profile.employeeData && profile.employeeData.employeeCount) { if (profile.employer && profile.employer.name) {
html += `<tr><td><strong>${__('Employer')}</strong></td><td class="text-content">${profile.employer.name}</td></tr>`;
if (profile.employer.position) {
html += `<tr><td><strong>${__('Employer Position')}</strong></td><td class="text-content">${profile.employer.position}</td></tr>`;
}
}
if (profile.isChiefOfAnyLegalEntity !== undefined) {
html += `<tr><td><strong>${__('Is Chief of Any Legal Entity')}</strong></td><td class="text-content">${profile.isChiefOfAnyLegalEntity ? __('Yes') : __('No')}</td></tr>`;
}
if (profile.employeeData && profile.employeeData.employeeCount !== undefined) {
html += `<tr><td><strong>${__('Employee Count')}</strong></td><td class="text-content">${profile.employeeData.employeeCount}</td></tr>`; html += `<tr><td><strong>${__('Employee Count')}</strong></td><td class="text-content">${profile.employeeData.employeeCount}</td></tr>`;
} }
html += '</table></div>'; html += '</table></div>';
// Important Dates
if (profile.suspensionStartDate || profile.suspensionEndDate || profile.liquidationDate) {
html += '<div class="section"><h5>' + __('Important Dates') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">';
if (profile.suspensionStartDate) {
const suspStartDate = moment(profile.suspensionStartDate).format('DD.MM.YYYY');
html += `<tr><td><strong>${__('Suspension Start Date')}</strong></td><td class="text-content">${suspStartDate}</td></tr>`;
}
if (profile.suspensionEndDate) {
const suspEndDate = moment(profile.suspensionEndDate).format('DD.MM.YYYY');
html += `<tr><td><strong>${__('Suspension End Date')}</strong></td><td class="text-content">${suspEndDate}</td></tr>`;
}
if (profile.liquidationDate) {
const liqDate = moment(profile.liquidationDate).format('DD.MM.YYYY');
html += `<tr><td><strong>${__('Liquidation Date')}</strong></td><td class="text-content">${liqDate}</td></tr>`;
}
html += '</table></div>';
}
// Certificates Information (if any)
if (profile.certificates && profile.certificates.length > 0) {
html += '<div class="section"><h5>' + __('Certificates') + '</h5>';
html += '<table class="table table-bordered table-sm profile-table">';
profile.certificates.forEach((cert, index) => {
html += `<tr><td colspan="2"><strong>${__('Certificate')} ${index + 1}</strong></td></tr>`;
if (cert.taxpayerType) {
html += `<tr><td>${__('Type')}</td><td class="text-content">${cert.taxpayerType}</td></tr>`;
}
if (cert.legalInfo) {
if (cert.legalInfo.tin) html += `<tr><td>${__('TIN')}</td><td class="text-content">${cert.legalInfo.tin}</td></tr>`;
if (cert.legalInfo.name) html += `<tr><td>${__('Name')}</td><td class="text-content">${cert.legalInfo.name}</td></tr>`;
}
if (cert.individualInfo) {
if (cert.individualInfo.fin) html += `<tr><td>${__('FIN')}</td><td class="text-content">${cert.individualInfo.fin}</td></tr>`;
if (cert.individualInfo.name) html += `<tr><td>${__('Name')}</td><td class="text-content">${cert.individualInfo.name}</td></tr>`;
}
if (cert.position) {
html += `<tr><td>${__('Position')}</td><td class="text-content">${cert.position}</td></tr>`;
}
if (cert.hasAccess !== undefined) {
html += `<tr><td>${__('Has Access')}</td><td class="text-content">${cert.hasAccess ? __('Yes') : __('No')}</td></tr>`;
}
if (cert.liquidated !== undefined) {
html += `<tr><td>${__('Liquidated')}</td><td class="text-content">${cert.liquidated ? __('Yes') : __('No')}</td></tr>`;
}
});
html += '</table></div>';
} }
} catch (e) { } catch (e) {
@ -1184,7 +1540,7 @@ window.ETaxesCompany = {
html += '</div>'; html += '</div>';
// CSS с нормальным переносом строк // CSS остается тем же
html = ` html = `
<style> <style>
.etaxes-profile-info { .etaxes-profile-info {

View File

@ -1,21 +1,21 @@
{ {
"actions": [], "actions": [],
"creation": "2025-06-05 14:25:30.955525", "creation": "2025-06-16 21:29:11.777085",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"section_break_t7i9" "section_break_twaq"
], ],
"fields": [ "fields": [
{ {
"fieldname": "section_break_t7i9", "fieldname": "section_break_twaq",
"fieldtype": "Section Break" "fieldtype": "Section Break"
} }
], ],
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-06-05 14:34:53.228349", "modified": "2025-06-16 21:29:44.750246",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Taxes Az", "module": "Taxes Az",
"name": "Business Classification", "name": "Business Classification",

View File

@ -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": []
}

View File

@ -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

View File

@ -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": []
}

View File

@ -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

View File

@ -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": []
}

View File

@ -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

View File

@ -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": []
}

View File

@ -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

View File

@ -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": []
}

View File

@ -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

View File

@ -1,33 +1,424 @@
// Код для экспорта XML
frappe.ui.form.on('Declaration of value added tax', { frappe.ui.form.on('Declaration of value added tax', {
refresh: function(frm) { refresh: function(frm) {
frm.add_custom_button(__('Export XML'), function() { // Добавляем кнопку автозаполнения
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);
}
);
}
}
});
// Функция автоматического заполнения
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({ frappe.call({
method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml', method: 'taxes_az.declaration_of_value_added_tax.fill_declaration_of_value_added_tax',
args: { args: {
mapping_tool: 'Declaration of value added tax', // Замените на имя вашего документа XML Mapping Tool declaration_name: frm.doc.name,
doctype: 'Declaration of value added tax', // Замените на имя DocType company: company,
docname: frm.docname quarter: frm.doc.rüb,
year: frm.doc.il
}, },
callback: function(r) { callback: function(r) {
if (r.message) { frappe.hide_progress();
// Создаем временный элемент для скачивания
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);
if (r.message && r.message.status === 'success') {
frm.reload_doc();
// Показываем результат
let msg = `
<div>
<p><strong>Bəyannamə uğurla dolduruldu!</strong></p>
<p>Dövriyyə: ${format_currency(r.message.total_turnover)} AZN</p>
<p>Hesablanmış ƏDV: ${format_currency(r.message.total_vat)} AZN</p>
<p>Əvəzləşdirilən ƏDV: ${format_currency(r.message.offset_vat)} AZN</p>
<hr>
<p><strong>${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</strong></p>
</div>
`;
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({ frappe.show_alert({
message: __('XML file generated and downloaded'), message: __('Bütün cədvəllər təmizləndi'),
indicator: 'green' indicator: 'blue'
}); });
}
// Показ результатов валидации
function show_validation_results(errors, warnings) {
let html = '<div>';
if (errors.length > 0) {
html += '<h4 style="color: red;">Xətalar:</h4><ul>';
errors.forEach(err => {
html += `<li>${err}</li>`;
});
html += '</ul>';
} }
if (warnings.length > 0) {
html += '<h4 style="color: orange;">Xəbərdarlıqlar:</h4><ul>';
warnings.forEach(warn => {
html += `<li>${warn}</li>`;
});
html += '</ul>';
}
if (errors.length === 0 && warnings.length === 0) {
html += '<p style="color: green;">Heç bir xəta tapılmadı!</p>';
}
html += '</div>';
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);
} }
}); });
}, __('Actions'));
// Считаем зачетный НДС
(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')
});

View File

@ -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 # For license information, please see license.txt
# import frappe import frappe
from frappe.model.document import Document 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): class Declarationofvalueaddedtax(Document):
pass 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
}