test commit

This commit is contained in:
Ali 2025-05-31 15:10:49 +04:00
parent eff297ad81
commit 6a4a65f24f
36 changed files with 1598 additions and 199 deletions

View File

@ -19,6 +19,15 @@ fixtures = [
}
]
doctype_js = {
"XML Mapping Tool": [
"public/js/xml_mapping/xml_parser.js",
"public/js/xml_mapping/field_generator.js",
"public/js/xml_mapping/doctype_field_selector.js",
"public/js/xml_mapping/mapping_storage.js",
"public/js/xml_mapping/xml_generator.js"
]
}
# Includes in <head>

View File

@ -0,0 +1,115 @@
// taxes_az/public/js/xml_mapping/doctype_field_selector.js
frappe.provide("taxes_az.xml_mapping");
taxes_az.xml_mapping.DoctypeFieldSelector = class DoctypeFieldSelector {
constructor(frm) {
this.frm = frm;
}
// Получить список полей DocType
async getFieldsForDoctype(doctype) {
return new Promise((resolve, reject) => {
frappe.model.with_doctype(doctype, () => {
try {
const meta = frappe.get_meta(doctype);
if (!meta) {
throw new Error(`DocType ${doctype} not found`);
}
const fields = meta.fields.filter(field => {
// Исключаем служебные поля и разделы
return !['Section Break', 'Column Break', 'Tab Break', 'HTML', 'Button'].includes(field.fieldtype);
});
resolve(fields);
} catch (error) {
reject(error);
}
});
});
}
// Добавить селекторы полей для каждого XML-пути
async attachFieldSelectors(container, xmlPaths) {
if (!this.frm.doc.target_doctype) {
frappe.throw(__('Please select a target DocType first'));
return;
}
try {
const fields = await this.getFieldsForDoctype(this.frm.doc.target_doctype);
// Добавляем селектор для каждого XML-пути
container.find('.xml-field-row').each((i, row) => {
const $row = $(row);
const xmlPath = $row.data('path');
const $selector = $row.find('.xml-field-selector');
// Создаем выпадающий список
const select = document.createElement('select');
select.classList.add('form-control', 'xml-field-map-select');
select.dataset.xmlPath = xmlPath;
// Опция "Не сопоставлять"
const defaultOption = document.createElement('option');
defaultOption.value = "";
defaultOption.textContent = __("-- Not mapped --");
select.appendChild(defaultOption);
// Добавляем опции для каждого поля DocType
fields.forEach(field => {
const option = document.createElement('option');
option.value = field.fieldname;
option.textContent = `${field.label || field.fieldname} (${field.fieldname})`;
select.appendChild(option);
});
// Добавляем селектор в контейнер
$selector.empty().append(select);
// Устанавливаем выбранное значение из сохраненных данных
if (this.frm.doc.mapping_data) {
try {
const mappingData = JSON.parse(this.frm.doc.mapping_data);
if (mappingData[xmlPath]) {
select.value = mappingData[xmlPath];
}
} catch (e) {
console.error('Error parsing mapping data:', e);
}
}
// Обработчик изменения
$(select).on('change', () => {
this._saveMappingData();
});
});
} catch (error) {
frappe.throw(`Error loading DocType fields: ${error.message}`);
}
}
_saveMappingData() {
const mappingData = {};
$('.xml-field-map-select').each((i, select) => {
const $select = $(select);
const xmlPath = $select.data('xml-path');
const fieldname = $select.val();
if (fieldname) {
mappingData[xmlPath] = fieldname;
}
});
// Просто устанавливаем значение, но не сохраняем автоматически
this.frm.set_value('mapping_data', JSON.stringify(mappingData));
// Уведомление об обновлении маппинга
frappe.show_alert({
message: __('Mapping updated. Remember to save the document.'),
indicator: 'blue'
});
}
};

View File

@ -0,0 +1,167 @@
// taxes_az/public/js/xml_mapping/field_generator.js
frappe.provide("taxes_az.xml_mapping");
taxes_az.xml_mapping.FieldGenerator = class FieldGenerator {
constructor(frm) {
this.frm = frm;
this.fieldContainer = $('<div class="xml-fields-container"></div>');
}
// Очистить все динамически созданные поля
clearFields() {
this.fieldContainer.empty();
if (this.section) {
this.section.remove();
}
}
// Создать UI для выбора полей
createFieldsUI(xmlStructure) {
// Проверяем, существует ли уже секция
if ($('.xml-mapping-section').length) {
this.clearFields();
} else {
this.clearFields();
// Создаем секцию для полей XML
this.section = $('<div class="xml-mapping-section frappe-card"></div>');
const sectionTitle = $('<h5 class="frappe-card-header p-3">' + __('XML Fields Mapping') + '</h5>');
this.section.append(sectionTitle);
// Добавляем поле поиска
const searchField = $(`
<div class="form-group p-3">
<div class="input-group">
<input type="text" class="form-control xml-search-field" placeholder="${__('Search in XML fields...')}">
<div class="input-group-append">
<button class="btn btn-default xml-search-button">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
`);
this.section.append(searchField);
this.section.append(this.fieldContainer);
// Добавляем секцию в форму после основной области формы
$('.form-layout').after(this.section);
// Добавляем обработчик поиска
searchField.find('.xml-search-field, .xml-search-button').on('keyup click', () => {
const searchText = searchField.find('.xml-search-field').val().toLowerCase();
this.fieldContainer.find('.xml-field-row').each((i, row) => {
const $row = $(row);
const path = $row.find('.xml-field-path').text().toLowerCase();
const value = $row.find('.xml-field-value').text().toLowerCase();
if (path.includes(searchText) || value.includes(searchText)) {
$row.show();
} else {
$row.hide();
}
});
});
}
// Для каждого пути создаем элемент интерфейса
xmlStructure.fieldPaths.forEach(fieldInfo => {
this._createFieldElement(fieldInfo);
});
// Инициализируем селектор DocType полей
if (this.frm.doc.target_doctype) {
const fieldSelector = new taxes_az.xml_mapping.DoctypeFieldSelector(this.frm);
fieldSelector.attachFieldSelectors(this.fieldContainer, xmlStructure.fieldPaths);
}
// Добавляем стили для полей
this._addStyles();
}
clearFields() {
this.fieldContainer = $('<div class="xml-fields-container p-3"></div>');
// Если секция существует, очищаем только контейнер полей
if ($('.xml-mapping-section').length) {
$('.xml-fields-container').replaceWith(this.fieldContainer);
}
}
clearFields() {
this.fieldContainer = $('<div class="xml-fields-container p-3"></div>');
if (this.section) {
this.section.remove();
}
}
clearFields() {
this.fieldContainer = $('<div class="xml-fields-container"></div>');
if (this.section) {
this.section.remove();
}
}
// Создание отдельного элемента поля
_createFieldElement(fieldInfo) {
const fieldRow = $(`
<div class="xml-field-row" data-path="${fieldInfo.path}">
<div class="xml-field-path">${fieldInfo.path}</div>
<div class="xml-field-value">${fieldInfo.value}</div>
<div class="xml-field-selector"></div>
</div>
`);
this.fieldContainer.append(fieldRow);
}
// Добавление стилей для UI полей
_addStyles() {
if (!$('#xml_mapping_styles').length) {
$('head').append(`
<style id="xml_mapping_styles">
.xml-fields-container {
margin-top: 15px;
max-height: 600px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}
.xml-field-row {
display: flex;
margin-bottom: 10px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
.xml-field-path {
flex: 1.5;
font-weight: bold;
overflow-wrap: break-word;
word-break: break-all;
padding-right: 10px;
}
.xml-field-value {
flex: 1;
color: #555;
margin: 0 10px;
overflow-wrap: break-word;
word-break: break-all;
}
.xml-field-selector {
flex: 1.5;
}
.xml-search-field {
margin-bottom: 10px;
}
</style>
`);
}
}
};

View File

@ -0,0 +1,117 @@
// taxes_az/public/js/xml_mapping/xml_generator.js
frappe.provide("taxes_az.xml_mapping");
taxes_az.xml_mapping.XMLGenerator = class XMLGenerator {
constructor(frm) {
this.frm = frm;
}
// Генерация XML на основе данных DocType и правил маппинга
async generateXML(doctypeName, docname) {
try {
// Загружаем данные DocType
const docData = await this._loadDocTypeData(doctypeName, docname);
// Загружаем правила маппинга
const mappingRules = JSON.parse(this.frm.doc.mapping_data || '{}');
// Загружаем шаблон XML
const xmlTemplate = this.frm.doc.xml_sample_file;
// Создаем DOM из шаблона XML
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlTemplate, "text/xml");
// Применяем правила маппинга
for (const xmlPath in mappingRules) {
const doctypeField = mappingRules[xmlPath];
const value = docData[doctypeField];
if (value !== undefined) {
this._setXMLValueByPath(xmlDoc, xmlPath, value);
}
}
// Преобразуем DOM обратно в строку XML
const serializer = new XMLSerializer();
const xmlString = serializer.serializeToString(xmlDoc);
return xmlString;
} catch (error) {
frappe.throw(`Error generating XML: ${error.message}`);
return null;
}
}
// Загрузка данных DocType
async _loadDocTypeData(doctypeName, docname) {
return new Promise((resolve, reject) => {
frappe.call({
method: 'frappe.client.get',
args: {
doctype: doctypeName,
name: docname
},
callback: (response) => {
if (response.message) {
resolve(response.message);
} else {
reject(new Error('Failed to load document data'));
}
}
});
});
}
// Установка значения в XML по пути
_setXMLValueByPath(xmlDoc, path, value) {
try {
// Проверяем, является ли путь атрибутом
if (path.includes('@')) {
const [elementPath, attrName] = path.split('@');
const elementPathParts = elementPath.split('/').filter(p => p);
// Получаем элемент
let element = xmlDoc.documentElement;
for (const part of elementPathParts) {
if (part !== element.nodeName) {
const elements = element.getElementsByTagName(part);
if (elements.length === 0) {
throw new Error(`Element ${part} not found in path ${path}`);
}
element = elements[0];
}
}
// Устанавливаем атрибут
element.setAttribute(attrName, value);
} else {
// Путь к элементу
const pathParts = path.split('/').filter(p => p);
// Получаем элемент
let element = xmlDoc.documentElement;
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i];
if (i === pathParts.length - 1) {
// Последний элемент в пути - целевой элемент
const elements = element.getElementsByTagName(part);
if (elements.length) {
elements[0].textContent = value;
}
} else if (part !== element.nodeName) {
const elements = element.getElementsByTagName(part);
if (elements.length === 0) {
throw new Error(`Element ${part} not found in path ${path}`);
}
element = elements[0];
}
}
}
} catch (error) {
console.error(`Error setting value for path ${path}:`, error);
}
}
};

View File

@ -0,0 +1,77 @@
// taxes_az/public/js/xml_mapping/xml_parser.js
frappe.provide("taxes_az.xml_mapping");
taxes_az.xml_mapping.XMLParser = class XMLParser {
constructor() {
this.fieldPaths = [];
}
// Основная функция парсинга XML
parseXML(xmlString) {
const parser = new DOMParser();
try {
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
// Проверка наличия ошибок парсинга
const parseError = xmlDoc.getElementsByTagName("parsererror");
if (parseError.length > 0) {
frappe.throw(__("Invalid XML: ") + parseError[0].textContent);
return null;
}
// Получить корневой элемент
const rootElement = xmlDoc.documentElement;
this.fieldPaths = [];
// Рекурсивно извлечь пути к полям
this._extractFieldPaths(rootElement, "");
return {
rootElement: rootElement.nodeName,
fieldPaths: this.fieldPaths
};
} catch (error) {
frappe.throw(__("Error parsing XML: ") + error.message);
return null;
}
}
// Рекурсивное извлечение путей к полям
_extractFieldPaths(element, currentPath) {
const nodeName = element.nodeName;
const newPath = currentPath ? `${currentPath}/${nodeName}` : nodeName;
// Если у элемента есть текстовое содержимое и нет дочерних элементов,
// считаем его полем
if (element.childNodes.length === 1 && element.childNodes[0].nodeType === 3) {
this.fieldPaths.push({
path: newPath,
value: element.textContent.trim(),
type: 'field'
});
return;
}
// Если у элемента есть атрибуты, извлекаем их как поля
if (element.attributes && element.attributes.length > 0) {
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
this.fieldPaths.push({
path: `${newPath}/@${attr.name}`,
value: attr.value,
type: 'attribute'
});
}
}
// Рекурсивно обрабатываем дочерние элементы
const childElements = element.childNodes;
for (let i = 0; i < childElements.length; i++) {
const child = childElements[i];
if (child.nodeType === 1) { // ELEMENT_NODE
this._extractFieldPaths(child, newPath);
}
}
}
};

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Building construction and simplified tax declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Building construction and simplified tax declaration', {
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: 'Building construction and simplified tax declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Building construction and simplified tax declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Certificate on non-emergence of tax liability", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Certificate on non-emergence of tax liability', {
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: 'Certificate on non-emergence of tax liability', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Certificate on non-emergence of tax liability', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Declaration of mining tax", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Declaration of mining 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 mining tax', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Declaration of mining 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'));
}
}
);

View File

@ -42,7 +42,7 @@
"topdansat\u0131\u015f_qiym\u0259ti_il\u0259_tab",
"html_hken",
"section_break_oytj",
"table_rhfx",
"topdansat\u0131\u015f_qiym\u0259ti_il\u0259",
"723b\u00fcdc\u0259y\u0259\u00f6d\u0259nilm\u0259livergininm\u0259bl\u0259\u011fi",
"faydal\u0131_qaz\u0131nt\u0131lar\u0131n_hasilat\u0131n_miqdar\u0131_il\u0259_tab",
"html_ybie",
@ -236,11 +236,6 @@
"fieldname": "section_break_oytj",
"fieldtype": "Section Break"
},
{
"fieldname": "table_rhfx",
"fieldtype": "Table",
"options": "Declaration of mining tax Topdansatis qiymeti"
},
{
"fieldname": "section_break_rpno",
"fieldtype": "Section Break"
@ -283,11 +278,16 @@
"fieldname": "717hesablanm\u0131\u015fvergim\u0259bl\u0259\u011fininc\u0259mi",
"fieldtype": "Int",
"label": "717. Hesablanm\u0131\u015f vergi m\u0259bl\u0259\u011finin c\u0259mi"
},
{
"fieldname": "topdansat\u0131\u015f_qiym\u0259ti_il\u0259",
"fieldtype": "Table",
"options": "Declaration of mining tax Topdansatis qiymeti"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-01-12 17:23:11.436483",
"modified": "2025-04-22 22:09:55.802593",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "Declaration of mining tax",

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Declaration of simplified tax", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Declaration of simplified 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 simplified tax', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Declaration of simplified 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Declaration of simplified tax on cash withdrawals", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Declaration of simplified tax on cash withdrawals', {
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 simplified tax on cash withdrawals', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Declaration of simplified tax on cash withdrawals', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Declaration of value added tax", {
// refresh(frm) {
// },
// });
// Код для экспорта 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Declaration of withholding tax on winnings", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Declaration of withholding tax on winnings', {
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 withholding tax on winnings', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Declaration of withholding tax on winnings', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2024, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Excise declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Excise declaration', {
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: 'Excise declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Excise declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2024, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Export declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Export declaration', {
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: 'Export declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Export declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Housing construction simplified tax declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Housing construction simplified tax declaration', {
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: 'Housing construction simplified tax declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Housing construction simplified tax declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Income tax return", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Income tax return', {
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: 'Income tax return', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Income tax return', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Income tax return of a private notary", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Income tax return of a private notary', {
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: 'Income tax return of a private notary', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Income tax return of a private notary', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Land tax declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Land tax declaration', {
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: 'Land tax declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Land tax declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Profit tax with special tax regime", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Profit tax with special tax regime', {
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: 'Profit tax with special tax regime', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Profit tax with special tax regime', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Property tax return", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Property tax return', {
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: 'Property tax return', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Property tax return', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Quarterly report on unemployment insurance", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Quarterly report on unemployment insurance', {
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: 'Quarterly report on unemployment insurance', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Quarterly report on unemployment insurance', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Real estate simplified tax declaration", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Real estate simplified tax declaration', {
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: 'Real estate simplified tax declaration', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Real estate simplified tax declaration', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("REFERENCE on controlled transactions", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('REFERENCE on controlled transactions', {
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: 'REFERENCE on controlled transactions', // Замените на имя вашего документа XML Mapping Tool
doctype: 'REFERENCE on controlled transactions', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Report on collection of state duty", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Report on collection of state duty', {
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: 'Report on collection of state duty', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Report on collection of state duty', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Road tax 2", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Road tax 2', {
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: 'Road tax 2', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Road tax 2', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Single declaration related to salaried and non-salaried work", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Single declaration related to salaried and non-salaried work', {
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: 'Single declaration related to salaried and non-salaried work', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Single declaration related to salaried and non-salaried work', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Tax return withheld at source of payment", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Tax return withheld at source of payment', {
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: 'Tax return withheld at source of payment', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Tax return withheld at source of payment', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("VAT calculated on the amount paid to a non-resident", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('VAT calculated on the amount paid to a non-resident', {
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: 'VAT calculated on the amount paid to a non-resident', // Замените на имя вашего документа XML Mapping Tool
doctype: 'VAT calculated on the amount paid to a non-resident', // Замените на имя 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'));
}
}
);

View File

@ -1,8 +1,33 @@
// Copyright (c) 2025, Jey Soft and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Withholding tax return of foreign subcontractors", {
// refresh(frm) {
// },
// });
// Код для экспорта XML
frappe.ui.form.on('Withholding tax return of foreign subcontractors', {
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: 'Withholding tax return of foreign subcontractors', // Замените на имя вашего документа XML Mapping Tool
doctype: 'Withholding tax return of foreign subcontractors', // Замените на имя 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'));
}
}
);

View File

@ -0,0 +1,30 @@
# Copyright (c) 2025, Jey Soft and Contributors
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
# On IntegrationTestCase, the doctype test records and all
# link-field test record depdendencies are recursively loaded
# Use these module variables to add/remove to/from that list
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
class UnitTestXMLMappingTool(UnitTestCase):
"""
Unit tests for XMLMappingTool.
Use this class for testing individual functions and methods.
"""
pass
class IntegrationTestXMLMappingTool(IntegrationTestCase):
"""
Integration tests for XMLMappingTool.
Use this class for testing interactions between multiple components.
"""
pass

View File

@ -0,0 +1,86 @@
frappe.ui.form.on('XML Mapping Tool', {
refresh: function(frm) {
// Инициализация объектов
frm.xmlParser = new taxes_az.xml_mapping.XMLParser();
frm.fieldGenerator = new taxes_az.xml_mapping.FieldGenerator(frm);
frm.doctypeFieldSelector = new taxes_az.xml_mapping.DoctypeFieldSelector(frm);
// Добавление кнопки для парсинга XML
frm.add_custom_button(__('Parse XML'), function() {
const xmlContent = frm.doc.xml_sample_file;
if (!xmlContent) {
frappe.throw(__('Please enter XML content first'));
return;
}
// Парсинг XML и создание полей
const xmlStructure = frm.xmlParser.parseXML(xmlContent);
if (xmlStructure) {
// Сохраняем структуру в поле формы
frm.set_value('xml_structure', JSON.stringify(xmlStructure));
// Создаем UI для полей
frm.fieldGenerator.createFieldsUI(xmlStructure);
frappe.show_alert({
message: __('XML successfully parsed'),
indicator: 'green'
});
}
}).addClass('btn-primary');
// Если структура XML уже загружена и выбран целевой DocType,
// только тогда обновляем UI и только если секция еще не создана
if (frm.doc.xml_structure && frm.doc.target_doctype && !$('.xml-mapping-section').length) {
try {
const xmlStructure = JSON.parse(frm.doc.xml_structure);
frm.fieldGenerator.createFieldsUI(xmlStructure);
} catch (e) {
console.error('Error parsing XML structure:', e);
}
}
// Добавляем информационное сообщение о процессе использования
if (!frm.doc.__islocal) {
frm.set_intro(`
<div class="alert alert-info">
<p><strong>${__('How to use this tool:')}</strong></p>
<ol>
<li>${__('Paste your XML sample into the XML Content field')}</li>
<li>${__('Click "Parse XML" button')}</li>
<li>${__('Select a Target DocType')}</li>
<li>${__('Map XML fields to DocType fields using the dropdown selectors')}</li>
<li>${__('Save your mapping settings by clicking the Save button')}</li>
<li>${__('Use the client-side script in your target DocType to add the export button')}</li>
</ol>
</div>
`);
}
// Добавляем кнопку сохранения для явного сохранения маппинга
frm.add_custom_button(__('Save Mapping'), function() {
frm.save();
}).addClass('btn-primary');
},
target_doctype: function(frm) {
// При изменении целевого DocType, нам нужно обновить только выпадающие списки,
// но не пересоздавать всю секцию
if (frm.doc.xml_structure && frm.doc.target_doctype) {
try {
const xmlStructure = JSON.parse(frm.doc.xml_structure);
// Если секция уже существует, просто обновим выпадающие списки
if ($('.xml-mapping-section').length) {
const fieldSelector = new taxes_az.xml_mapping.DoctypeFieldSelector(frm);
fieldSelector.attachFieldSelectors($('.xml-fields-container'), xmlStructure.fieldPaths);
} else {
// Если секции еще нет, создаем полный UI
frm.fieldGenerator.createFieldsUI(xmlStructure);
}
} catch (e) {
console.error('Error parsing XML structure:', e);
}
}
}
});

View File

@ -0,0 +1,74 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-04-18 18:47:47.135855",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"tittle",
"xml_sample_file",
"target_doctype",
"is_active",
"xml_structure",
"mapping_data"
],
"fields": [
{
"fieldname": "tittle",
"fieldtype": "Data",
"label": "Tittle"
},
{
"fieldname": "xml_sample_file",
"fieldtype": "Text",
"ignore_xss_filter": 1,
"label": "XML Sample File"
},
{
"fieldname": "target_doctype",
"fieldtype": "Link",
"label": "Target Doctype",
"options": "DocType"
},
{
"default": "0",
"fieldname": "is_active",
"fieldtype": "Check",
"label": "Is active"
},
{
"fieldname": "xml_structure",
"fieldtype": "Long Text",
"label": "Xml structure"
},
{
"fieldname": "mapping_data",
"fieldtype": "JSON",
"label": "Mapping data"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-04-22 12:58:25.456299",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "XML Mapping Tool",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,124 @@
# taxes_az/taxes_az/doctype/xml_mapping_tool/xml_mapping_tool.py
import frappe
import json
from frappe.model.document import Document
from frappe import _
from xml.etree import ElementTree as ET
class XMLMappingTool(Document):
pass
@frappe.whitelist()
def generate_xml(mapping_tool, doctype, docname):
try:
# Получаем документ с правилами маппинга
mapping = frappe.get_doc("XML Mapping Tool", mapping_tool)
# Проверяем наличие необходимых данных
if not mapping.xml_sample_file or not mapping.mapping_data:
frappe.throw(_("Mapping tool doesn't have sample XML or mapping data"))
# Получаем данные документа
doc_data = frappe.get_doc(doctype, docname)
# Применяем маппинг для создания XML
try:
root = ET.fromstring(mapping.xml_sample_file)
except Exception as e:
frappe.throw(_("Error parsing XML template: {0}").format(str(e)))
# Загружаем правила маппинга
mapping_rules = json.loads(mapping.mapping_data)
# Применяем правила маппинга
for xml_path, field_name in mapping_rules.items():
if not field_name:
continue
try:
# Получаем значение поля из документа
field_value = doc_data.get(field_name)
# Обрабатываем путь XML
if '@' in xml_path: # Это атрибут
element_path, attr_name = xml_path.rsplit('@', 1)
element_path = element_path.rstrip('/')
# Находим элемент по пути
element = _find_element_by_path(root, element_path)
if element is not None:
element.set(attr_name, str(field_value) if field_value is not None else '')
else: # Это обычный элемент
# Находим элемент по пути
element = _find_element_by_path(root, xml_path)
if element is not None:
element.text = str(field_value) if field_value is not None else ''
except Exception as e:
frappe.log_error(_("Error setting value for {0}: {1}").format(xml_path, str(e)))
# Конвертируем обратно в строку
xml_string = ET.tostring(root, encoding='utf-8', method='xml').decode('utf-8')
# Добавляем XML декларацию
xml_declaration = '<?xml version="1.0" encoding="UTF-8"?>\n'
xml_string = xml_declaration + xml_string
return xml_string
except Exception as e:
frappe.log_error(_("Error generating XML: {0}").format(str(e)))
frappe.throw(_("Error generating XML: {0}").format(str(e)))
def _find_element_by_path(root, path):
"""
Находит элемент по пути XPath в XML дереве.
Эта функция нужна, поскольку простой root.find() не всегда правильно работает
с вложенными путями.
"""
if not path:
return None
# Удаляем имя корневого элемента из пути, если оно там есть
if path.startswith(root.tag + '/'):
path = path[len(root.tag) + 1:]
# Разбиваем путь на компоненты
components = path.split('/')
# Начинаем с корневого элемента
current_element = root
# Проходим по каждому компоненту пути
for i, component in enumerate(components):
if not component: # Пропускаем пустые компоненты
continue
if component == current_element.tag:
# Если текущий компонент соответствует тегу элемента, просто продолжаем
continue
# Для путей с множественными элементами (например, row в HesaplamaUzreEnt)
# мы должны найти все подходящие элементы и выбрать нужный
# Для простоты, возьмем первый найденный
found = False
for child in current_element.findall('./'):
if child.tag == component:
current_element = child
found = True
break
if not found:
# Пробуем найти через XPath напрямую
try:
result = current_element.find('.//' + component)
if result is not None:
current_element = result
found = True
except:
pass
if not found:
# Если элемент не найден, возвращаем None
return None
return current_element