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 e118b92..c146918 100644 --- a/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js +++ b/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js @@ -82,7 +82,6 @@ function save_formulas(frm) { frm.set_value("formula_storage", JSON.stringify(formula_data)); } -// Генерация клиентского скрипта function generate_client_script(frm) { if (!frm.doc.target_doctype) { console.error("❌ Target Doctype отсутствует!"); @@ -100,7 +99,6 @@ function generate_client_script(frm) { // Получаем информацию о таблицах DocType getTableDoctypes(target_doctype, table_doctypes); - console.log("📊 Соответствие таблиц и DocType:", table_doctypes); // Сбор всех табличных частей и их полей @@ -135,7 +133,48 @@ function generate_client_script(frm) { } }); - // Обрабатываем все формулы и их триггеры + // Собираем информацию о табличных триггерах - ключ: table_field, значение: массив полей основного доктайпа + const table_field_triggers = {}; + + // Проходим по всем формулам, ищем триггеры вида tablename_fieldname + Object.keys(formula_data).forEach(fieldname => { + let field_info = formula_data[fieldname]; + if (!field_info.is_table_field && field_info.triggers) { + let triggers = field_info.triggers.split(',').map(t => t.trim()); + + triggers.forEach(trigger => { + if (trigger.includes('_')) { + const parts = trigger.split('_'); + if (parts.length >= 2) { + const tableName = parts[0]; + const fieldName = parts.slice(1).join('_'); + + // Добавляем информацию о триггере + if (!table_field_triggers[trigger]) { + table_field_triggers[trigger] = []; + } + + // Сохраняем поле основного документа и признак асинхронности + table_field_triggers[trigger].push({ + main_field: fieldname, + uses_universal: field_info.formula.includes("univ(") + }); + } + } + }); + } + }); + + // Собираем все триггеры из табличных частей для последующей фильтрации + const tableTriggersSet = new Set(); + Object.keys(table_field_triggers).forEach(trigger => { + tableTriggersSet.add(trigger); + }); + + // Отдельно собираем все поля, которые используют предопределенное событие "При открытии документа" + const refreshHandlers = []; + + // Обрабатываем все формулы для основного документа Object.keys(formula_data).forEach(fieldname => { let field_info = formula_data[fieldname]; @@ -209,12 +248,33 @@ try { }`); } - // Добавляем триггеры для полей с непустыми триггерами + // Проверяем триггеры if (field_info.triggers) { let triggers = field_info.triggers.split(',').map(t => t.trim()); - triggers.forEach(trigger => { + + // Проверяем наличие предопределенного события "При открытии документа" + const hasRefreshTrigger = triggers.some(trigger => isPredefinedEvent(trigger)); + + if (hasRefreshTrigger) { + // Добавляем в список обработчиков refresh + refreshHandlers.push({ + field: fieldname, + uses_universal: uses_universal + }); + + // Удаляем предопределенное событие из списка триггеров + triggers = triggers.filter(trigger => !isPredefinedEvent(trigger)); + } + + // Фильтруем триггеры, исключая те, что уже добавлены в table_field_triggers + const filteredTriggers = triggers.filter(trigger => !tableTriggersSet.has(trigger)); + + // Удаляем дубликаты триггеров + const uniqueTriggers = [...new Set(filteredTriggers)]; + + uniqueTriggers.forEach(trigger => { if (trigger) { - // Если формула использует универсальные поля, триггеры должны быть асинхронными + // Добавляем обработчик для обычного поля if (uses_universal) { main_script += ` "${trigger}": async function(frm) { await calculate_${fieldname}(frm); },\n`; } else { @@ -225,69 +285,242 @@ try { } } }); + + // Добавляем обработчик refresh, если были найдены формулы с триггером "При открытии документа" + if (refreshHandlers.length > 0) { + // Создаем обработчик refresh с вызовом всех нужных функций расчета + let refreshHandlerBody = ` "refresh": ${refreshHandlers.some(h => h.uses_universal) ? 'async ' : ''}function(frm) {\n`; + + // Добавляем вызовы всех обработчиков + refreshHandlers.forEach(handler => { + if (handler.uses_universal) { + refreshHandlerBody += ` await calculate_${handler.field}(frm);\n`; + } else { + refreshHandlerBody += ` calculate_${handler.field}(frm);\n`; + } + }); + + refreshHandlerBody += ' },\n'; + + // Добавляем обработчик refresh в начало скрипта + main_script = main_script.replace('frappe.ui.form.on("' + target_doctype + '", {\n', + 'frappe.ui.form.on("' + target_doctype + '", {\n' + refreshHandlerBody); + } main_script += `});`; - // Добавляем обработчики для табличных частей + // Напрямую добавляем скрипты для дочерних таблиц + let child_scripts = ''; + + // Создаем список имеющихся таблиц из триггеров + const tableTriggers = {}; + Object.keys(table_field_triggers).forEach(trigger => { + if (trigger.includes('_')) { + const parts = trigger.split('_'); + const tableName = parts[0]; + const fieldName = parts.slice(1).join('_'); + + if (!tableTriggers[tableName]) { + tableTriggers[tableName] = {}; + } + + if (!tableTriggers[tableName][fieldName]) { + tableTriggers[tableName][fieldName] = []; + } + + // Добавляем все поля основного документа, зависящие от этого поля таблицы + table_field_triggers[trigger].forEach(item => { + tableTriggers[tableName][fieldName].push(item); + }); + } + }); + + // Для каждой таблицы создаем отдельный скрипт + Object.keys(tableTriggers).forEach(tableName => { + // Определяем DocType дочерней таблицы + const childDoctype = table_doctypes[tableName] || `${target_doctype} Item`; + + // Начинаем скрипт + let tableScript = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; + + // Добавляем обработчики для каждого поля + Object.keys(tableTriggers[tableName]).forEach(fieldName => { + const handlers = tableTriggers[tableName][fieldName]; + const hasAsyncHandler = handlers.some(h => h.uses_universal); + + // Создаем обработчик исходя из асинхронности + if (hasAsyncHandler) { + tableScript += ` "${fieldName}": async function(frm, cdt, cdn) {\n`; + + // Добавляем вызовы всех обработчиков + handlers.forEach(handler => { + if (handler.uses_universal) { + tableScript += ` await calculate_${handler.main_field}(frm);\n`; + } else { + tableScript += ` calculate_${handler.main_field}(frm);\n`; + } + }); + + tableScript += ` },\n`; + } else { + tableScript += ` "${fieldName}": function(frm, cdt, cdn) {\n`; + + // Добавляем вызовы всех обработчиков + handlers.forEach(handler => { + tableScript += ` calculate_${handler.main_field}(frm);\n`; + }); + + tableScript += ` },\n`; + } + }); + + // Закрываем скрипт таблицы + tableScript += `});`; + child_scripts += tableScript; + }); + + // Добавляем скрипты дочерних таблиц к основному скрипту + main_script += child_scripts; + + 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); +} + +// Новая функция для генерации скриптов дочерних таблиц с расширенным логированием +function generate_child_table_script(table_child_scripts, table_doctypes, table_fields_triggers, formula_data, functions) { + let child_scripts = ''; + + console.log("🔍 DEBUG: Вход в функцию generate_child_table_script"); + console.log("🔍 DEBUG: table_child_scripts:", JSON.stringify(table_child_scripts, null, 2)); + console.log("🔍 DEBUG: table_fields_triggers:", JSON.stringify(table_fields_triggers, null, 2)); + + // Обрабатываем каждую табличную часть 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 uses_universal = table_child_scripts[table_name].uses_universal; + const childInfo = table_child_scripts[table_name]; + const childDoctype = childInfo.doctype; + const uses_universal = childInfo.uses_universal; - // Создаем общую функцию для расчета формул в строке таблицы - let calcFunction; + console.log(`🔍 DEBUG: Генерируем скрипт для таблицы ${table_name}, DocType: ${childDoctype}`); + // Начинаем скрипт для дочерней таблицы + let child_script = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; + + // Функция расчета для строки табличной части + let calcFunctionBody = ''; + + // Добавляем асинхронность, если есть универсальные поля if (uses_universal) { - calcFunction = ` + calcFunctionBody = ` async function calculate_${table_name}_row(frm, cdt, cdn) { let row = locals[cdt][cdn]; try { // Сначала вычисляем универсальные поля await calculate_universal_fields(frm); - `; +`; } else { - calcFunction = ` + calcFunctionBody = ` function calculate_${table_name}_row(frm, cdt, cdn) { let row = locals[cdt][cdn]; try {`; } - + // Добавляем расчеты для каждого поля табличной части - table_child_scripts[table_name].calculations.forEach(calc => { - calcFunction += ` + childInfo.calculations.forEach(calc => { + calcFunctionBody += ` // Расчет для поля ${calc.field} row["${calc.field}"] = eval(transform_expression("${calc.formula}", true, "${table_name}")); - `; +`; }); - calcFunction += ` + calcFunctionBody += ` frm.refresh_field("${table_name}"); } catch (e) { console.error("❌ Ошибка в формуле для таблицы ${table_name}:", e); } }`; - - functions.push(calcFunction); - - // Добавляем обработчики для каждого триггерного поля - Array.from(table_child_scripts[table_name].fields).forEach(field => { - if (uses_universal) { - table_script += ` "${field}": async function(frm, cdt, cdn) { await calculate_${table_name}_row(frm, cdt, cdn); },\n`; + + functions.push(calcFunctionBody); + + console.log(`🔍 DEBUG: Поля-триггеры в таблице ${table_name}:`, Array.from(childInfo.fields).join(', ')); + + // Собираем все уникальные имена полей из триггеров таблицы для основного документа + const allTableFieldTriggers = {}; + if (table_fields_triggers[table_name]) { + Object.keys(table_fields_triggers[table_name]).forEach(fieldName => { + allTableFieldTriggers[fieldName] = table_fields_triggers[table_name][fieldName]; + }); + } + + console.log(`🔍 DEBUG: Триггеры полей для основного документа из таблицы ${table_name}:`, Object.keys(allTableFieldTriggers).join(', ')); + + // Добавляем обработчики для триггеров табличной части + Array.from(childInfo.fields).forEach(field => { + console.log(`🔍 DEBUG: Обрабатываем поле-триггер ${field} в таблице ${table_name}`); + + // Проверяем, есть ли этот триггер в table_fields_triggers + const isMainDocumentTrigger = allTableFieldTriggers[field] !== undefined; + + console.log(`🔍 DEBUG: Триггер ${field} ${isMainDocumentTrigger ? 'НАЙДЕН' : 'НЕ НАЙДЕН'} в списке триггеров основного документа`); + + // Если есть триггер, который обновляет поля основного документа + if (isMainDocumentTrigger) { + const triggers = allTableFieldTriggers[field]; + + console.log(`🔍 DEBUG: Для триггера ${field} найдено ${triggers.length} обработчиков основного документа`); + + const triggerCodes = triggers.map(t => { + const code = t.uses_universal ? + `await calculate_${t.main_field}(frm);` : + `calculate_${t.main_field}(frm);`; + console.log(`🔍 DEBUG: Генерируем код для поля ${t.main_field}: ${code}`); + return code; + }); + + if (uses_universal) { + child_script += ` "${field}": async function(frm, cdt, cdn) { + // Сначала обновляем значения в текущей строке + await calculate_${table_name}_row(frm, cdt, cdn); + + // Затем пересчитываем поля основного документа + ${triggerCodes.join('\n ')} + },\n`; + } else { + child_script += ` "${field}": function(frm, cdt, cdn) { + // Сначала обновляем значения в текущей строке + calculate_${table_name}_row(frm, cdt, cdn); + + // Затем пересчитываем поля основного документа + ${triggers.map(t => { + if (t.uses_universal) { + return `// Асинхронная функция требует await, вызываем через setTimeout + setTimeout(async function() { await calculate_${t.main_field}(frm); }, 0);`; } else { - table_script += ` "${field}": function(frm, cdt, cdn) { calculate_${table_name}_row(frm, cdt, cdn); },\n`; + return `calculate_${t.main_field}(frm);`; + } + }).join('\n ')} + },\n`; + } + } else { + // Стандартное поведение - только обновление текущей строки + if (uses_universal) { + child_script += ` "${field}": async function(frm, cdt, cdn) { await calculate_${table_name}_row(frm, cdt, cdn); },\n`; + } else { + child_script += ` "${field}": function(frm, cdt, cdn) { calculate_${table_name}_row(frm, cdt, cdn); },\n`; + } } }); - table_script += `});`; - main_script += table_script; + // Закрываем скрипт табличной части + child_script += `});`; + child_scripts += child_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); + + console.log("🔍 DEBUG: Результат generate_child_table_script:", child_scripts); + return child_scripts; } // Вспомогательные функции для generate_client_script @@ -830,6 +1063,7 @@ function createNewFormulaScript(target_doctype, script_code, script_name) { // UI компоненты - создание и обработка полей function generate_dynamic_fields(frm, doctype, callback) { + // Получаем числовые поля для формул frappe.call({ method: "formula_editor.api.get_doctype_fields", args: { doctype: doctype }, @@ -838,75 +1072,110 @@ function generate_dynamic_fields(frm, doctype, callback) { return; } - let fields = r.message; - - if (!frm.fields_dict.formula_fields) { - return; - } - - let wrapper = $(frm.fields_dict.formula_fields.wrapper); - wrapper.empty(); - - // Группировка полей - let grouped_fields = { "Основные поля": [] }; - let table_fieldnames = new Set(); + let formula_fields = r.message; - fields.forEach(field => { - if (!field.fieldname) return; - - let label = field.label ? field.label : "*Нет наименования*"; - let match = label.match(/^(.*?)\s*→\s*(.*?)$/); - let table_name = match ? match[1] : field.fieldname.startsWith("table_") ? field.fieldname : "Основные поля"; - let field_label = match ? match[2] : label; - - if (!grouped_fields[table_name]) { - 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); + // Получаем все подходящие поля для триггеров + frappe.call({ + method: "formula_editor.api.get_all_doctype_fields", + args: { doctype: doctype }, + callback: function(trigger_response) { + let trigger_fields = trigger_response.message || []; + + if (!frm.fields_dict.formula_fields) { + return; + } + + let wrapper = $(frm.fields_dict.formula_fields.wrapper); + wrapper.empty(); + + // Группировка полей для формул + let grouped_fields = { "Основные поля": [] }; + let table_fieldnames = new Set(); + + formula_fields.forEach(field => { + if (!field.fieldname) return; + + let label = field.label ? field.label : "*Нет наименования*"; + let match = label.match(/^(.*?)\s*→\s*(.*?)$/); + let table_name = match ? match[1] : field.fieldname.startsWith("table_") ? field.fieldname : "Основные поля"; + let field_label = match ? match[2] : label; + + if (!grouped_fields[table_name]) { + grouped_fields[table_name] = []; + } + grouped_fields[table_name].push({ fieldname: field.fieldname, label: field_label, fieldtype: field.fieldtype }); + + if (match || field.fieldname.startsWith("table_")) { + table_fieldnames.add(field.fieldname); + } + }); + + // Группировка полей для триггеров + let grouped_trigger_fields = { "Основные поля": [] }; + + trigger_fields.forEach(field => { + if (!field.fieldname) return; + + let label = field.label ? field.label : "*Нет наименования*"; + let match = label.match(/^(.*?)\s*→\s*(.*?)$/); + let table_name = match ? match[1] : field.fieldname.startsWith("table_") ? field.fieldname : "Основные поля"; + let field_label = match ? match[2] : label; + + if (!grouped_trigger_fields[table_name]) { + grouped_trigger_fields[table_name] = []; + } + grouped_trigger_fields[table_name].push({ + fieldname: field.fieldname, + label: field_label, + fieldtype: field.fieldtype, + options: field.options + }); + }); + + // Сохраняем списки полей в frm для использования в триггерах + frm.trigger_fields = trigger_fields; + frm.grouped_trigger_fields = grouped_trigger_fields; + + let container = $('
'); + + // Создаем модальные окна + createModals(); + + // Добавляем секцию универсальных полей + addUniversalFieldsSection(wrapper); + + // Создаем поля для каждой группы + Object.keys(grouped_fields).forEach(group => { + let section_name = group === "Основные поля" ? group : `${group} (${group.replace(/\s+/g, '_').toLowerCase()})`; + let section = $(''); + section.append(`