diff --git a/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js b/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js index 0bb94c9..cc445d4 100644 --- a/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js +++ b/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js @@ -1,369 +1,369 @@ frappe.ui.form.on('Formula Editor', { - onload_post_render: function(frm) { - if (!frm.is_initialized) { - frm.is_initialized = true; - console.log("Initializing Formula Editor"); +onload_post_render: function(frm) { + if (!frm.is_initialized) { + frm.is_initialized = true; + console.log("Initializing Formula Editor"); - if (frm.doc.target_doctype) { - generate_dynamic_fields(frm, frm.doc.target_doctype, function() { - if (frm.doc.formula_storage) { - restore_formulas(frm); - } - }); - } + if (frm.doc.target_doctype) { + generate_dynamic_fields(frm, frm.doc.target_doctype, function() { + if (frm.doc.formula_storage) { + restore_formulas(frm); + } + }); } - }, - - target_doctype: function(frm) { - generate_dynamic_fields(frm, frm.doc.target_doctype, function() { - restore_formulas(frm); - }); - }, - - before_save: function(frm) { - console.log("📝 `before_save` вызван. Проверяем сохраненные формулы..."); - save_formulas(frm); - - console.log("🔄 `formula_storage` перед сохранением документа:", frm.doc.formula_storage); - generate_client_script(frm); } +}, + +target_doctype: function(frm) { + generate_dynamic_fields(frm, frm.doc.target_doctype, function() { + restore_formulas(frm); + }); +}, + +before_save: function(frm) { + console.log("📝 `before_save` вызван. Проверяем сохраненные формулы..."); + save_formulas(frm); + + console.log("🔄 `formula_storage` перед сохранением документа:", frm.doc.formula_storage); + generate_client_script(frm); +} }); function restore_formulas(frm) { - let stored_formulas = JSON.parse(frm.doc.formula_storage || "{}"); - console.log("🔄 Восстанавливаем формулы:", stored_formulas); +let stored_formulas = JSON.parse(frm.doc.formula_storage || "{}"); +console.log("🔄 Восстанавливаем формулы:", stored_formulas); - $(frm.fields_dict.formula_fields.wrapper).find('.formula-input, .trigger-input').each(function () { - let fieldname = $(this).attr("data-fieldname"); +$(frm.fields_dict.formula_fields.wrapper).find('.formula-input, .trigger-input').each(function () { + let fieldname = $(this).attr("data-fieldname"); - if (!fieldname) return; + if (!fieldname) return; - let isTriggerField = fieldname.endsWith("_triggers"); - let baseFieldname = isTriggerField ? fieldname.replace("_triggers", "") : fieldname; + let isTriggerField = fieldname.endsWith("_triggers"); + let baseFieldname = isTriggerField ? fieldname.replace("_triggers", "") : fieldname; - if (stored_formulas[baseFieldname] !== undefined) { - let value = isTriggerField ? stored_formulas[baseFieldname].triggers : stored_formulas[baseFieldname].formula; + if (stored_formulas[baseFieldname] !== undefined) { + let value = isTriggerField ? stored_formulas[baseFieldname].triggers : stored_formulas[baseFieldname].formula; - if (typeof value !== "string") { - console.warn(`⚠️ Поле "${fieldname}" имеет некорректное значение:`, value); - return; - } - - console.log(`✔️ Восстанавливаем "${fieldname}" -> "${value}"`); - $(this).val(value); - } - }); -} - -function save_formulas(frm) { - let formula_data = {}; - - console.log("🔄 Начинаем сохранение формул и триггеров..."); - - $(frm.fields_dict.formula_fields.wrapper).find('.formula-input').each(function () { - let fieldname = $(this).attr("data-fieldname"); - let formula = $(this).val(); - let is_table_field = $(this).attr("data-is-table-field") === "true"; - - // Ищем триггерное поле - let triggers = $(frm.fields_dict.formula_fields.wrapper) - .find(`[data-fieldname="${fieldname}_triggers"] input`).val(); - - console.log(`📌 Обрабатываем поле: "${fieldname}"`); - console.log(` 🔹 Формула: "${formula}"`); - console.log(` 🔹 Триггеры: "${triggers}"`); - console.log(` 🔹 Табличное поле: "${is_table_field}"`); - - formula_data[fieldname] = { - formula: formula || "", - triggers: triggers || "", - is_table_field: is_table_field - }; - }); - - console.log("✅ Итоговый JSON formula_storage:", formula_data); - frm.set_value("formula_storage", JSON.stringify(formula_data)); -} - -function generate_client_script(frm) { - if (!frm.doc.target_doctype) { - console.error("❌ Target Doctype отсутствует!"); - return; - } - - let target_doctype = frm.doc.target_doctype; - let formula_data = JSON.parse(frm.doc.formula_storage || "{}"); - - let main_script = `frappe.ui.form.on("${target_doctype}", {\n`; - let functions = []; - let table_child_scripts = {}; - - // Справочник соответствия таблиц и их DocType - let table_doctypes = {}; - - // Сначала соберем информацию о табличных полях и их DocType - frappe.call({ - method: "frappe.client.get", - args: { - doctype: "DocType", - name: target_doctype - }, - async: false, // Синхронный вызов для гарантии получения данных - callback: function(r) { - if (r.message && r.message.child_tables) { - r.message.child_tables.forEach(table => { - if (table.options) { - // Сохраняем соответствие имени поля таблицы в родительском DocType и DocType самой таблицы - table_doctypes[table.fieldname] = table.options; - } - }); - } - } - }); - - // Если данные о таблицах не получены, пробуем альтернативный способ через meta - if (Object.keys(table_doctypes).length === 0) { - const meta = frappe.get_meta(target_doctype); - if (meta && meta.fields) { - meta.fields.forEach(field => { - if (field.fieldtype === 'Table' && field.options) { - table_doctypes[field.fieldname] = field.options; - } - }); - } - } - - console.log("📊 Соответствие таблиц и DocType:", table_doctypes); - - // Сбор всех табличных частей и их полей - let child_tables = {}; - - Object.keys(formula_data).forEach(fieldname => { - let field_info = formula_data[fieldname]; - - if (field_info && field_info.is_table_field) { - // Извлекаем имя табличной части из fieldname (формат: tablename_fieldname) - let parts = fieldname.split('_'); - if (parts.length >= 2) { - let table_name = parts[0]; - if (!child_tables[table_name]) { - child_tables[table_name] = []; - } - child_tables[table_name].push(fieldname); - } - } - }); - - Object.keys(formula_data).forEach(fieldname => { - let field_info = formula_data[fieldname]; - - if (!field_info || typeof field_info !== "object" || !field_info.formula) { - console.warn(`⚠️ Пропущено поле ${fieldname}, так как оно не содержит формулу.`); + if (typeof value !== "string") { + console.warn(`⚠️ Поле "${fieldname}" имеет некорректное значение:`, value); return; } - let formula = field_info.formula.trim(); - let triggers = field_info.triggers ? field_info.triggers.split(',').map(t => t.trim()) : []; - let is_table_field = field_info.is_table_field; + console.log(`✔️ Восстанавливаем "${fieldname}" -> "${value}"`); + $(this).val(value); + } +}); +} - function transform_expression(expression, for_child_table = false, table_name = null) { - console.log("🔄 Исходная формула:", expression, "для табличной части:", for_child_table, "таблица:", table_name); - - let transformed = expression; - - // Если мы трансформируем выражение для табличной части - if (for_child_table) { - // Заменяем val() на row() для всех полей внутри табличной части - transformed = transformed.replace(/val\((\w+)\)/g, (match, field) => { - // Проверяем, если имя поля содержит префикс таблицы - if (field.startsWith(table_name + '_')) { - // Извлекаем имя поля без префикса - const actualFieldName = field.substring(table_name.length + 1); - return `row["${actualFieldName}"]`; - } - // Если поле не из текущей таблицы, используем frm.doc - return `frm.doc["${field}"]`; - }); - - // Обрабатываем row() как обычно для табличной части - transformed = transformed.replace(/row\((\w+)\)/g, (match, field) => { - return `row["${field}"]`; - }); - } else { - // Стандартная замена для обычных полей - transformed = transformed.replace(/val\((\w+)\)/g, (match, field) => { - return `frm.doc["${field}"]`; - }); - - // Предупреждение, если row() используется вне таблицы - transformed = transformed.replace(/row\((\w+)\)/g, (match, field) => { - console.warn("⚠️ Функция row() используется вне контекста табличной части"); - return `0 /* ERROR: row() вне табличной части */`; - }); - } - - // Функция суммирования по табличной части применяется в обоих случаях - transformed = transformed.replace(/sum\((\w+)_(\w+)\)/g, (match, tbl, fld) => { - return `(frm.doc["${tbl}"].reduce((sum, row) => sum + (row["${fld}"] || 0), 0))`; +function save_formulas(frm) { +let formula_data = {}; + +console.log("🔄 Начинаем сохранение формул и триггеров..."); + +$(frm.fields_dict.formula_fields.wrapper).find('.formula-input').each(function () { + let fieldname = $(this).attr("data-fieldname"); + let formula = $(this).val(); + let is_table_field = $(this).attr("data-is-table-field") === "true"; + + // Ищем триггерное поле + let triggers = $(frm.fields_dict.formula_fields.wrapper) + .find(`[data-fieldname="${fieldname}_triggers"] input`).val(); + + console.log(`📌 Обрабатываем поле: "${fieldname}"`); + console.log(` 🔹 Формула: "${formula}"`); + console.log(` 🔹 Триггеры: "${triggers}"`); + console.log(` 🔹 Табличное поле: "${is_table_field}"`); + + formula_data[fieldname] = { + formula: formula || "", + triggers: triggers || "", + is_table_field: is_table_field + }; +}); + +console.log("✅ Итоговый JSON formula_storage:", formula_data); +frm.set_value("formula_storage", JSON.stringify(formula_data)); +} + +function generate_client_script(frm) { +if (!frm.doc.target_doctype) { + console.error("❌ Target Doctype отсутствует!"); + return; +} + +let target_doctype = frm.doc.target_doctype; +let formula_data = JSON.parse(frm.doc.formula_storage || "{}"); + +let main_script = `frappe.ui.form.on("${target_doctype}", {\n`; +let functions = []; +let table_child_scripts = {}; + +// Справочник соответствия таблиц и их DocType +let table_doctypes = {}; + +// Сначала соберем информацию о табличных полях и их DocType +frappe.call({ + method: "frappe.client.get", + args: { + doctype: "DocType", + name: target_doctype + }, + async: false, // Синхронный вызов для гарантии получения данных + callback: function(r) { + if (r.message && r.message.child_tables) { + r.message.child_tables.forEach(table => { + if (table.options) { + // Сохраняем соответствие имени поля таблицы в родительском DocType и DocType самой таблицы + table_doctypes[table.fieldname] = table.options; + } }); - - // Обработка процентов - transformed = transformed.replace(/(frm\.doc\["\w+"\]|\d+|row\["\w+"\])\s*([+\-])\s*(\d+(\.\d+)?)%/g, (match, base, operator, percent) => { - console.log("✅ Обнаружен процент в выражении:", match); - return `${base} ${operator} ((${base} * ${percent}) / 100)`; - }); - - console.log("🔄 Трансформированная формула:", transformed); - return transformed; } + } +}); - let modified_formula = transform_expression(formula, is_table_field); - - if (is_table_field) { - // Определяем имя табличной части из fieldname - let parts = fieldname.split('_'); - let table_name = parts[0]; - let field_name_in_table = parts.slice(1).join('_'); - - // Создаем или дополняем обработчики для конкретной табличной части - if (!table_child_scripts[table_name]) { - table_child_scripts[table_name] = { - doctype: table_doctypes[table_name] || `${target_doctype} Item`, // По умолчанию используем Parent DocType + Item - calculations: [], - fields: new Set() - }; +// Если данные о таблицах не получены, пробуем альтернативный способ через meta +if (Object.keys(table_doctypes).length === 0) { + const meta = frappe.get_meta(target_doctype); + if (meta && meta.fields) { + meta.fields.forEach(field => { + if (field.fieldtype === 'Table' && field.options) { + table_doctypes[field.fieldname] = field.options; } + }); + } +} - // Добавляем функцию расчета для текущего поля - table_child_scripts[table_name].calculations.push({ - field: field_name_in_table, - formula: transform_expression(formula, true, table_name) +console.log("📊 Соответствие таблиц и DocType:", table_doctypes); + +// Сбор всех табличных частей и их полей +let child_tables = {}; + +Object.keys(formula_data).forEach(fieldname => { + let field_info = formula_data[fieldname]; + + if (field_info && field_info.is_table_field) { + // Извлекаем имя табличной части из fieldname (формат: tablename_fieldname) + let parts = fieldname.split('_'); + if (parts.length >= 2) { + let table_name = parts[0]; + if (!child_tables[table_name]) { + child_tables[table_name] = []; + } + child_tables[table_name].push(fieldname); + } + } +}); + +Object.keys(formula_data).forEach(fieldname => { + let field_info = formula_data[fieldname]; + + if (!field_info || typeof field_info !== "object" || !field_info.formula) { + console.warn(`⚠️ Пропущено поле ${fieldname}, так как оно не содержит формулу.`); + return; + } + + let formula = field_info.formula.trim(); + let triggers = field_info.triggers ? field_info.triggers.split(',').map(t => t.trim()) : []; + let is_table_field = field_info.is_table_field; + + function transform_expression(expression, for_child_table = false, table_name = null) { + console.log("🔄 Исходная формула:", expression, "для табличной части:", for_child_table, "таблица:", table_name); + + let transformed = expression; + + // Если мы трансформируем выражение для табличной части + if (for_child_table) { + // Заменяем val() на row() для всех полей внутри табличной части + transformed = transformed.replace(/val\((\w+)\)/g, (match, field) => { + // Проверяем, если имя поля содержит префикс таблицы + if (field.startsWith(table_name + '_')) { + // Извлекаем имя поля без префикса + const actualFieldName = field.substring(table_name.length + 1); + return `row["${actualFieldName}"]`; + } + // Если поле не из текущей таблицы, используем frm.doc + return `frm.doc["${field}"]`; }); - - // Добавляем триггеры - triggers.forEach(trigger => { - table_child_scripts[table_name].fields.add(trigger); + + // Обрабатываем row() как обычно для табличной части + transformed = transformed.replace(/row\((\w+)\)/g, (match, field) => { + return `row["${field}"]`; }); } else { - // Обычная функция расчета для поля основного документа - functions.push(` -function calculate_${fieldname}(frm) { - try { - let result = ${modified_formula}; - frm.set_value("${fieldname}", result); - } catch (e) { - console.error("❌ Ошибка в формуле для ${fieldname}: ${formula}", e); - } -}`); - - // Добавляем триггеры для основного документа - triggers.forEach(trigger => { - main_script += ` "${trigger}": function(frm) { calculate_${fieldname}(frm); },\n`; + // Стандартная замена для обычных полей + transformed = transformed.replace(/val\((\w+)\)/g, (match, field) => { + return `frm.doc["${field}"]`; + }); + + // Предупреждение, если row() используется вне таблицы + transformed = transformed.replace(/row\((\w+)\)/g, (match, field) => { + console.warn("⚠️ Функция row() используется вне контекста табличной части"); + return `0 /* ERROR: row() вне табличной части */`; }); } - }); - - main_script += `});`; - - // Добавляем обработчики для табличных частей - Object.keys(table_child_scripts).forEach(table_name => { - let childDoctype = table_child_scripts[table_name].doctype; - let table_script = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; - // Создаем общую функцию для расчета формул в строке таблицы - let calcFunction = ` -function calculate_${table_name}_row(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - - try {`; - - // Добавляем расчеты для каждого поля табличной части с правильным форматированием - table_child_scripts[table_name].calculations.forEach(calc => { - calcFunction += ` - // Расчет для поля ${calc.field} - row["${calc.field}"] = ${calc.formula}; -`; + // Функция суммирования по табличной части применяется в обоих случаях + transformed = transformed.replace(/sum\((\w+)_(\w+)\)/g, (match, tbl, fld) => { + return `(frm.doc["${tbl}"].reduce((sum, row) => sum + (row["${fld}"] || 0), 0))`; }); - calcFunction += ` - frm.refresh_field("${table_name}"); - } catch (e) { - console.error("❌ Ошибка в формуле для таблицы ${table_name}:", e); + // Обработка процентов + transformed = transformed.replace(/(frm\.doc\["\w+"\]|\d+|row\["\w+"\])\s*([+\-])\s*(\d+(\.\d+)?)%/g, (match, base, operator, percent) => { + console.log("✅ Обнаружен процент в выражении:", match); + return `${base} ${operator} ((${base} * ${percent}) / 100)`; + }); + + console.log("🔄 Трансформированная формула:", transformed); + return transformed; } + + let modified_formula = transform_expression(formula, is_table_field); + + if (is_table_field) { + // Определяем имя табличной части из fieldname + let parts = fieldname.split('_'); + let table_name = parts[0]; + let field_name_in_table = parts.slice(1).join('_'); + + // Создаем или дополняем обработчики для конкретной табличной части + if (!table_child_scripts[table_name]) { + table_child_scripts[table_name] = { + doctype: table_doctypes[table_name] || `${target_doctype} Item`, // По умолчанию используем Parent DocType + Item + calculations: [], + fields: new Set() + }; + } + + // Добавляем функцию расчета для текущего поля + table_child_scripts[table_name].calculations.push({ + field: field_name_in_table, + formula: transform_expression(formula, true, table_name) + }); + + // Добавляем триггеры + triggers.forEach(trigger => { + table_child_scripts[table_name].fields.add(trigger); + }); + } else { + // Обычная функция расчета для поля основного документа + functions.push(` +function calculate_${fieldname}(frm) { +try { + let result = ${modified_formula}; + frm.set_value("${fieldname}", result); +} catch (e) { + console.error("❌ Ошибка в формуле для ${fieldname}: ${formula}", e); +} +}`); + + // Добавляем триггеры для основного документа + triggers.forEach(trigger => { + main_script += ` "${trigger}": function(frm) { calculate_${fieldname}(frm); },\n`; + }); + } +}); + +main_script += `});`; + +// Добавляем обработчики для табличных частей +Object.keys(table_child_scripts).forEach(table_name => { + let childDoctype = table_child_scripts[table_name].doctype; + let table_script = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; + + // Создаем общую функцию для расчета формул в строке таблицы + let calcFunction = ` +function calculate_${table_name}_row(frm, cdt, cdn) { +let row = locals[cdt][cdn]; + +try {`; + + // Добавляем расчеты для каждого поля табличной части с правильным форматированием + table_child_scripts[table_name].calculations.forEach(calc => { + calcFunction += ` + // Расчет для поля ${calc.field} + row["${calc.field}"] = ${calc.formula}; +`; + }); + + calcFunction += ` + frm.refresh_field("${table_name}"); +} catch (e) { + console.error("❌ Ошибка в формуле для таблицы ${table_name}:", e); +} }`; - functions.push(calcFunction); + functions.push(calcFunction); - // Добавляем обработчики для каждого триггерного поля - Array.from(table_child_scripts[table_name].fields).forEach(field => { - table_script += ` "${field}": function(frm, cdt, cdn) { calculate_${table_name}_row(frm, cdt, cdn); },\n`; - }); - - table_script += `});`; - main_script += table_script; + // Добавляем обработчики для каждого триггерного поля + Array.from(table_child_scripts[table_name].fields).forEach(field => { + table_script += ` "${field}": function(frm, cdt, cdn) { calculate_${table_name}_row(frm, cdt, cdn); },\n`; }); + + table_script += `});`; + main_script += table_script; +}); - let final_script = `${main_script}\n\n${functions.join("\n\n")}`; - console.log("✅ **Сгенерированный Client Script:**\n", final_script); +let final_script = `${main_script}\n\n${functions.join("\n\n")}`; +console.log("✅ **Сгенерированный Client Script:**\n", final_script); - update_or_create_client_script(target_doctype, final_script); +update_or_create_client_script(target_doctype, final_script); } function update_or_create_client_script(target_doctype, script_code) { - frappe.call({ - method: "frappe.client.get_list", - args: { - doctype: "Client Script", - filters: { dt: target_doctype }, - fields: ["name"] - }, - callback: function(r) { - if (r.message && r.message.length > 0) { - let existing_script_name = r.message[0].name; - console.log(`🔄 Найден существующий Client Script: ${existing_script_name}. Обновляем...`); +frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Client Script", + filters: { dt: target_doctype }, + fields: ["name"] + }, + callback: function(r) { + if (r.message && r.message.length > 0) { + let existing_script_name = r.message[0].name; + console.log(`🔄 Найден существующий Client Script: ${existing_script_name}. Обновляем...`); - frappe.call({ - method: "frappe.client.set_value", - args: { + frappe.call({ + method: "frappe.client.set_value", + args: { + doctype: "Client Script", + name: existing_script_name, + fieldname: "script", + value: script_code + }, + callback: function(response) { + if (response.message) { + console.log(`✅ Client Script обновлен для ${target_doctype}`); + } else { + console.error("❌ Ошибка при обновлении Client Script", response); + } + } + }); + } else { + console.log("⚠️ Существующий Client Script не найден. Создаем новый..."); + + frappe.call({ + method: "frappe.client.insert", + args: { + doc: { doctype: "Client Script", - name: existing_script_name, - fieldname: "script", - value: script_code - }, - callback: function(response) { - if (response.message) { - console.log(`✅ Client Script обновлен для ${target_doctype}`); - } else { - console.error("❌ Ошибка при обновлении Client Script", response); - } + name: `Client Script for ${target_doctype}`, + dt: target_doctype, + script_type: "Client", + script: script_code, + enabled:1 } - }); - } else { - console.log("⚠️ Существующий Client Script не найден. Создаем новый..."); - - frappe.call({ - method: "frappe.client.insert", - args: { - doc: { - doctype: "Client Script", - name: `Client Script for ${target_doctype}`, - dt: target_doctype, - script_type: "Client", - script: script_code, - enabled:1 - } - }, - callback: function(response) { - if (response.message) { - console.log(`✅ Client Script создан для ${target_doctype}`); - } else { - console.error("❌ Ошибка при создании Client Script", response); - } + }, + callback: function(response) { + if (response.message) { + console.log(`✅ Client Script создан для ${target_doctype}`); + } else { + console.error("❌ Ошибка при создании Client Script", response); } - }); - } + } + }); } - }); + } +}); } function generate_dynamic_fields(frm, doctype, callback) { @@ -376,21 +376,21 @@ function generate_dynamic_fields(frm, doctype, callback) { console.warn("⚠️ Нет данных из API get_doctype_fields"); return; } - + let fields = r.message; console.log("✅ Получены поля:", fields); - + if (!frm.fields_dict.formula_fields) { console.error("❌ Поле 'formula_fields' не найдено!"); return; } - + let wrapper = $(frm.fields_dict.formula_fields.wrapper); wrapper.empty(); - + let grouped_fields = { "Основные поля": [] }; let table_fieldnames = new Set(); - + fields.forEach(field => { if (!field.fieldname) return; @@ -403,26 +403,29 @@ function generate_dynamic_fields(frm, doctype, callback) { grouped_fields[table_name] = []; } grouped_fields[table_name].push({ fieldname: field.fieldname, label: field_label }); - + if (match || field.fieldname.startsWith("table_")) { table_fieldnames.add(field.fieldname); } }); - + let container = $('
'); - + // Создаем модальное окно формульного редактора createFormulaBuilderModal(); - + + // Создаем модальное окно редактора полей изменения + createTriggerFieldsModal(); + Object.keys(grouped_fields).forEach(group => { let section_name = group === "Основные поля" ? group : `${group} (${group.replace(/\s+/g, '_').toLowerCase()})`; let section = $(''); section.append(`