diff --git a/formula_editor/api.py b/formula_editor/api.py index 5c848b0..d9aca4b 100644 --- a/formula_editor/api.py +++ b/formula_editor/api.py @@ -7,17 +7,29 @@ def get_doctype_fields(doctype): for field in meta.fields: if field.fieldtype in ["Int", "Float", "Currency"]: fields.append({"fieldname": field.fieldname, "label": field.label, "fieldtype": field.fieldtype}) - # Проверяем таблицы внутри DocType (например, items в Sales Invoice) + + # Проверяем таблицы внутри DocType for child in meta.fields: if child.fieldtype == "Table": child_meta = frappe.get_meta(child.options) for sub_field in child_meta.fields: if sub_field.fieldtype in ["Int", "Float", "Currency"]: + # Обычные поля табличной части (для суммирования) fields.append({ "fieldname": f"{child.fieldname}_{sub_field.fieldname}", "label": f"{child.label} → {sub_field.label}", "fieldtype": sub_field.fieldtype }) + + # Добавляем поля для конкретных строк таблицы (первые 10 строк) + for row_idx in range(1, 11): + fields.append({ + "fieldname": f"{child.fieldname}_{row_idx}_{sub_field.fieldname}", + "label": f"{child.label} → Строка {row_idx} → {sub_field.label}", + "fieldtype": sub_field.fieldtype, + "is_row_specific": True, + "row_index": row_idx + }) return fields @frappe.whitelist() 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 c146918..0edf6c3 100644 --- a/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js +++ b/formula_editor/formula_editor/doctype/formula_editor/formula_editor.js @@ -5,6 +5,13 @@ frappe.ui.form.on('Formula Editor', { frm.set_value("universal_fields_storage", "{}"); } + addTokenStyles() + + // Инициализация хранилища для формул строк табличных частей + if (!frm.doc.table_row_formulas_storage) { + frm.set_value("table_row_formulas_storage", "{}"); + } + if (!frm.is_initialized) { frm.is_initialized = true; @@ -30,7 +37,6 @@ frappe.ui.form.on('Formula Editor', { }, before_remove: function(frm) { - if (frm.doc.target_doctype) { delete_client_script_for_doctype(frm.doc.target_doctype); } @@ -58,6 +64,24 @@ function restore_formulas(frm) { $(this).val(value); } }); + + // Убедимся, что предопределенные события обрабатываются при первой загрузке формы + if (Object.keys(stored_formulas).length > 0) { + let hasRefreshTriggers = false; + + // Проверяем, есть ли формулы с триггером "При открытии документа" + Object.keys(stored_formulas).forEach(fieldname => { + const formula = stored_formulas[fieldname]; + if (formula && formula.triggers && formula.triggers.includes("При открытии документа")) { + hasRefreshTriggers = true; + } + }); + + // Если нашли такие формулы, вызываем обработчик refresh для их выполнения + if (hasRefreshTriggers && typeof frm.trigger === 'function') { + setTimeout(() => frm.trigger('refresh'), 100); + } + } } function save_formulas(frm) { @@ -68,15 +92,18 @@ function save_formulas(frm) { 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(); - formula_data[fieldname] = { - formula: formula || "", - triggers: triggers || "", - is_table_field: is_table_field - }; + // Проверяем, что формула или триггеры не пустые + if (formula.trim() !== "" || (triggers && triggers.trim() !== "")) { + formula_data[fieldname] = { + formula: formula || "", + triggers: triggers || "", + is_table_field: is_table_field + }; + } }); frm.set_value("formula_storage", JSON.stringify(formula_data)); @@ -84,13 +111,13 @@ function save_formulas(frm) { 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 universal_fields = JSON.parse(frm.doc.universal_fields_storage || "{}"); + let table_row_formulas = JSON.parse(frm.doc.table_row_formulas_storage || "{}"); let main_script = `// Generated by Formula Editor\nfrappe.ui.form.on("${target_doctype}", {\n`; let functions = []; @@ -99,7 +126,8 @@ function generate_client_script(frm) { // Получаем информацию о таблицах DocType getTableDoctypes(target_doctype, table_doctypes); - console.log("📊 Соответствие таблиц и DocType:", table_doctypes); + + // Проверяем содержимое table_row_formulas // Сбор всех табличных частей и их полей let child_tables = {}; @@ -122,6 +150,9 @@ function generate_client_script(frm) { // Добавляем функцию преобразования выражений functions.push(generateTransformExpressionFunction(universal_fields)); + // Добавляем функцию для работы с конкретными строками табличных частей + functions.push(generateTableRowAccessFunction()); + // Создаем список всех полей, которые используют универсальные поля let fieldsUsingUniversal = []; @@ -179,7 +210,7 @@ function generate_client_script(frm) { let field_info = formula_data[fieldname]; if (!field_info || typeof field_info !== "object" || !field_info.formula) { - console.warn(`⚠️ Пропущено поле ${fieldname}, так как оно не содержит формулу.`); + // console.warn(`⚠️ Пропущено поле ${fieldname}, так как оно не содержит формулу.`); return; } @@ -231,9 +262,14 @@ try { // Затем оцениваем формулу let result = eval(transform_expression("${formula}", false)); + + // Проверка на NaN и преобразование в 0 + if (isNaN(result)) { + result = 0; + } + frm.set_value("${fieldname}", result); } catch (e) { - console.error("❌ Ошибка в формуле для ${fieldname}: ${formula}", e); } }`); } else { @@ -241,9 +277,14 @@ try { function calculate_${fieldname}(frm) { try { let result = eval(transform_expression("${formula}", false)); + + // Проверка на NaN и преобразование в 0 + if (isNaN(result)) { + result = 0; + } + frm.set_value("${fieldname}", result); } catch (e) { - console.error("❌ Ошибка в формуле для ${fieldname}: ${formula}", e); } }`); } @@ -286,12 +327,27 @@ try { } }); + // Добавляем функции для расчета формул конкретных строк табличных частей + if (Object.keys(table_row_formulas).length > 0) { + // Создаем функции для расчета формул строк таблиц + generateTableRowFormulasHandlers(table_row_formulas, functions, target_doctype, universal_fields); + } + // Добавляем обработчик refresh, если были найдены формулы с триггером "При открытии документа" - if (refreshHandlers.length > 0) { + if (refreshHandlers.length > 0 || Object.keys(table_row_formulas).length > 0) { // Создаем обработчик refresh с вызовом всех нужных функций расчета let refreshHandlerBody = ` "refresh": ${refreshHandlers.some(h => h.uses_universal) ? 'async ' : ''}function(frm) {\n`; + // Если есть формулы для строк табличных частей, инициализируем их + if (Object.keys(table_row_formulas).length > 0) { + refreshHandlerBody += ` initialize_table_row_formulas(frm);\n`; + } + // Добавляем вызовы всех обработчиков + if (refreshHandlers.some(h => h.uses_universal)) { + refreshHandlerBody += ` await calculate_universal_fields(frm);\n`; + } + refreshHandlers.forEach(handler => { if (handler.uses_universal) { refreshHandlerBody += ` await calculate_${handler.field}(frm);\n`; @@ -306,226 +362,83 @@ try { main_script = main_script.replace('frappe.ui.form.on("' + target_doctype + '", {\n', 'frappe.ui.form.on("' + target_doctype + '", {\n' + refreshHandlerBody); } + + // Добавляем обработчики для событий табличных частей + main_script = addTableRowFormulaHandlers(main_script, table_row_formulas, table_doctypes); 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; - }); + const child_scripts = generate_child_table_script(table_child_scripts, table_doctypes, table_field_triggers, formula_data, functions); // Добавляем скрипты дочерних таблиц к основному скрипту 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) { +function generate_child_table_script(table_child_scripts, table_doctypes, table_field_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 => { - const childInfo = table_child_scripts[table_name]; - const childDoctype = childInfo.doctype; - const uses_universal = childInfo.uses_universal; + Object.keys(table_child_scripts).forEach(tableName => { + let childDoctype = table_child_scripts[tableName].doctype; + let usesUniversalFields = table_child_scripts[tableName].uses_universal; + let table_script = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; - console.log(`🔍 DEBUG: Генерируем скрипт для таблицы ${table_name}, DocType: ${childDoctype}`); - - // Начинаем скрипт для дочерней таблицы - let child_script = `\n\nfrappe.ui.form.on("${childDoctype}", {\n`; - - // Функция расчета для строки табличной части - let calcFunctionBody = ''; - - // Добавляем асинхронность, если есть универсальные поля - if (uses_universal) { - calcFunctionBody = ` -async function calculate_${table_name}_row(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - - try { - // Сначала вычисляем универсальные поля - await calculate_universal_fields(frm); -`; - } else { - calcFunctionBody = ` -function calculate_${table_name}_row(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - - try {`; + // Создаем общую функцию для расчета формул в строке таблицы + let calcFunction = ` + ${usesUniversalFields ? 'async ' : ''}function calculate_${tableName}_row(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + + try {`; + + // Если таблица использует универсальные поля, сначала вычисляем их + if (usesUniversalFields) { + calcFunction += ` + // Вычисляем универсальные поля + await calculate_universal_fields(frm); + `; } - + // Добавляем расчеты для каждого поля табличной части - childInfo.calculations.forEach(calc => { - calcFunctionBody += ` - // Расчет для поля ${calc.field} - row["${calc.field}"] = eval(transform_expression("${calc.formula}", true, "${table_name}")); -`; + table_child_scripts[tableName].calculations.forEach(calc => { + calcFunction += ` + // Расчет для поля ${calc.field} + row["${calc.field}"] = eval(transform_expression("${calc.formula}", true, "${tableName}")); + `; }); - calcFunctionBody += ` - frm.refresh_field("${table_name}"); - } catch (e) { - console.error("❌ Ошибка в формуле для таблицы ${table_name}:", e); - } -}`; - - 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 { - return `calculate_${t.main_field}(frm);`; + calcFunction += ` + frm.refresh_field("${tableName}"); + } catch (e) { } - }).join('\n ')} - },\n`; - } + }`; + + functions.push(calcFunction); + + // Добавляем обработчики для каждого триггерного поля + Array.from(table_child_scripts[tableName].fields).forEach(field => { + if (usesUniversalFields) { + table_script += ` "${field}": async function(frm, cdt, cdn) { await calculate_${tableName}_row(frm, cdt, cdn); },\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 += ` "${field}": function(frm, cdt, cdn) { calculate_${tableName}_row(frm, cdt, cdn); },\n`; } }); - // Закрываем скрипт табличной части - child_script += `});`; - child_scripts += child_script; + table_script += `});`; + child_scripts += table_script; }); - console.log("🔍 DEBUG: Результат generate_child_table_script:", child_scripts); return child_scripts; } // Вспомогательные функции для generate_client_script function getTableDoctypes(target_doctype, table_doctypes) { // Пытаемся получить информацию о таблицах через API + frappe.call({ method: "frappe.client.get", args: { @@ -534,18 +447,19 @@ function getTableDoctypes(target_doctype, table_doctypes) { }, async: false, callback: function(r) { - if (r.message && r.message.child_tables) { - r.message.child_tables.forEach(table => { - if (table.options) { - table_doctypes[table.fieldname] = table.options; + if (r.message && r.message.fields) { + r.message.fields.forEach(field => { + if (field.fieldtype === 'Table' && field.options) { + table_doctypes[field.fieldname] = field.options; } }); } } }); - // Если данные не получены, используем альтернативный способ + // Если данные не получены, используем альтернативный способ через meta if (Object.keys(table_doctypes).length === 0) { + const meta = frappe.get_meta(target_doctype); if (meta && meta.fields) { meta.fields.forEach(field => { @@ -555,6 +469,11 @@ function getTableDoctypes(target_doctype, table_doctypes) { }); } } + + // Проверяем результат + if (Object.keys(table_doctypes).length === 0) { + } else { + } } function collectChildTables(formula_data, child_tables) { @@ -583,7 +502,6 @@ function generateUniversalFieldsFunction(universal_fields) { ${Object.keys(universal_fields).map(fieldId => { const field = universal_fields[fieldId]; if (!field || !field.name || !field.doctype || !field.fieldname) { - console.warn(`⚠️ Некорректное универсальное поле с ID ${fieldId}:`, field); return ''; } @@ -629,14 +547,12 @@ function generateUniversalFieldsFunction(universal_fields) { // Проверяем, что поле ay имеет допустимое значение if (!docValues.ay || typeof docValues.ay !== 'string') { - console.warn("⚠️ Поле 'ay' пустое или имеет некорректный формат", docValues.ay); skipCalculation_${fieldId} = true; return; } // Проверяем, что поле il имеет допустимое значение if (!docValues.il || isNaN(parseInt(docValues.il))) { - console.warn("⚠️ Поле 'il' пустое или имеет некорректный формат", docValues.il); skipCalculation_${fieldId} = true; return; } @@ -648,13 +564,10 @@ function generateUniversalFieldsFunction(universal_fields) { // Добавляем два фильтра для диапазона дат filters_${fieldId}.push(["${filter.field}", ">=", dateRange.start]); filters_${fieldId}.push(["${filter.field}", "<=", dateRange.end]); - console.log("🔍 Применяем специальные фильтры по датам для ${field.name}:", dateRange); } else { - console.warn("⚠️ Не удалось обработать специальные поля месяца и года", docValues); skipCalculation_${fieldId} = true; } } else { - console.log("🔍 Пропуск расчета ${field.name}: поля 'ay' или 'il' не определены"); skipCalculation_${fieldId} = true; } `; @@ -664,7 +577,6 @@ function generateUniversalFieldsFunction(universal_fields) { if (frm.doc["${filter.docField}"] !== undefined) { filters_${fieldId}.push(["${filter.field}", "${filter.operator}", frm.doc["${filter.docField}"]]); } else { - console.log("⚠️ Поле '${filter.docField}' не определено в документе"); skipCalculation_${fieldId} = true; } `; @@ -680,14 +592,11 @@ function generateUniversalFieldsFunction(universal_fields) { // Проверяем, нужно ли пропустить расчет if (skipCalculation_${fieldId}) { - console.log("⚠️ Расчет ${field.name} пропущен из-за отсутствия или некорректности данных"); if (!frm.doc.__universalFields) frm.doc.__universalFields = {}; frm.doc.__universalFields["${fieldId}"] = 0; return; } - console.log("🔍 Применяемые фильтры для ${field.name}:", filters_${fieldId}); - const result_${fieldId} = await frappe.db.get_list("${field.doctype}", { fields: ["${field.fieldname}"], filters: filters_${fieldId}, @@ -703,10 +612,7 @@ function generateUniversalFieldsFunction(universal_fields) { // Сохраняем результат в временное поле документа if (!frm.doc.__universalFields) frm.doc.__universalFields = {}; frm.doc.__universalFields["${fieldId}"] = univ_${fieldId}; - - console.log("✅ Универсальное поле ${field.name} рассчитано:", univ_${fieldId}); } catch (error) { - console.error("❌ Ошибка при расчете универсального поля ${field.name}:", error); if (!frm.doc.__universalFields) frm.doc.__universalFields = {}; frm.doc.__universalFields["${fieldId}"] = 0; }`; @@ -741,7 +647,6 @@ function generateUniversalFieldsFunction(universal_fields) { // Получаем год из поля il или используем текущий год let year = docValues.il || new Date().getFullYear(); - console.log(\`🔹 Обработка специальных полей: ay=\${docValues.ay} (\${monthNum}), il=\${year}\`); // Определяем первый день месяца (1-е число) const firstDay = new Date(year, monthNum - 1, 1); @@ -753,8 +658,6 @@ function generateUniversalFieldsFunction(universal_fields) { const firstDayStr = \`\${year}.\${String(monthNum).padStart(2, '0')}.01\`; const lastDayStr = \`\${year}.\${String(monthNum).padStart(2, '0')}.\${String(lastDay.getDate()).padStart(2, '0')}\`; - console.log(\`🔹 Диапазон дат для \${docValues.ay} \${year}: \${firstDayStr} - \${lastDayStr}\`); - // Возвращаем объект с диапазоном дат return { start: firstDayStr, @@ -806,7 +709,6 @@ function generateTransformExpressionFunction(universal_fields) { return ` // Функция для преобразования выражений формул function transform_expression(expression, for_child_table = false, table_name = null) { - console.log("🔄 Исходная формула:", expression, "для табличной части:", for_child_table, "таблица:", table_name); let transformed = expression; @@ -832,7 +734,6 @@ function generateTransformExpressionFunction(universal_fields) { // Проверяем использование val() для табличных полей (должны использоваться sum()) const valTablePattern = new RegExp(\`val\\\\(\${tablePrefix}_([^)]+)\\\\)\`, 'g'); transformed = transformed.replace(valTablePattern, (match, fieldName) => { - console.warn(\`⚠️ Использование val() для табличного поля. Заменено на sum(): \${match}\`); return \`(frm.doc["\${tablePrefix}"] && frm.doc["\${tablePrefix}"].reduce((sum, row) => sum + (parseFloat(row["\${fieldName}"]) || 0), 0) || 0)\`; }); } @@ -845,36 +746,32 @@ function generateTransformExpressionFunction(universal_fields) { if (field.startsWith(table_name + '_')) { // Извлекаем имя поля без префикса const actualFieldName = field.substring(table_name.length + 1); - return \`row["\${actualFieldName}"]\`; + return \`(parseFloat(row["\${actualFieldName}"]) || 0)\`; } // Если поле не из текущей таблицы, используем frm.doc - return \`frm.doc["\${field}"]\`; + return \`(parseFloat(frm.doc["\${field}"]) || 0)\`; }); // Обрабатываем row() как обычно для табличной части transformed = transformed.replace(/row\\(([^)]+)\\)/g, (match, field) => { - return \`row["\${field}"]\`; + return \`(parseFloat(row["\${field}"]) || 0)\`; }); } else { // Стандартная замена для обычных полей transformed = transformed.replace(/val\\(([^)]+)\\)/g, (match, field) => { - return \`frm.doc["\${field}"]\`; + return \`(parseFloat(frm.doc["\${field}"]) || 0)\`; }); // Предупреждение, если row() используется вне таблицы transformed = transformed.replace(/row\\(([^)]+)\\)/g, (match, field) => { - console.warn("⚠️ Функция row() используется вне контекста табличной части"); return \`0 /* ERROR: row() вне табличной части */\`; }); } // Обработка процентов transformed = transformed.replace(/(frm\\.doc\\["[^"]+"\]|\\d+|row\\["[^"]+"\])\\s*([+\\-])\\s*(\\d+(\\.\\d+)?)%/g, (match, base, operator, percent) => { - console.log("✅ Обнаружен процент в выражении:", match); return \`\${base} \${operator} ((\${base} * \${percent}) / 100)\`; }); - - console.log("🔄 Трансформированная формула:", transformed); return transformed; }`; } @@ -1002,7 +899,6 @@ function update_or_create_client_script(target_doctype, script_code) { ); if (formula_script) { - console.log(`🔄 Найден существующий Formula Editor Script: ${formula_script.name}. Обновляем...`); frappe.call({ method: "frappe.client.set_value", @@ -1017,18 +913,14 @@ function update_or_create_client_script(target_doctype, script_code) { }, callback: function(response) { if (response.message) { - console.log(`✅ Formula Editor Script обновлен для ${target_doctype}`); } else { - console.error("❌ Ошибка при обновлении Formula Editor Script", response); } } }); } else { - console.log("⚠️ Существующий Formula Editor Script не найден. Создаем новый..."); createNewFormulaScript(target_doctype, script_code, formula_script_name); } } else { - console.log("⚠️ Клиентских скриптов для DocType не найдено. Создаем новый..."); createNewFormulaScript(target_doctype, script_code, formula_script_name); } } @@ -1053,9 +945,7 @@ function createNewFormulaScript(target_doctype, script_code, script_name) { }, callback: function(response) { if (response.message) { - console.log(`✅ Formula Editor Script создан для ${target_doctype} с именем ${script_name}`); } else { - console.error("❌ Ошибка при создании Formula Editor Script", response); } } }); @@ -1144,6 +1034,9 @@ function generate_dynamic_fields(frm, doctype, callback) { // Добавляем секцию универсальных полей addUniversalFieldsSection(wrapper); + // Добавляем секцию формул для строк табличных частей + addTableRowFormulasSection(wrapper); + // Создаем поля для каждой группы Object.keys(grouped_fields).forEach(group => { let section_name = group === "Основные поля" ? group : `${group} (${group.replace(/\s+/g, '_').toLowerCase()})`; @@ -1168,6 +1061,9 @@ function generate_dynamic_fields(frm, doctype, callback) { initUniversalFields(frm); renderUniversalFields(frm); + // Инициализируем формулы для строк табличных частей + initTableRowFormulas(frm); + // Прикрепляем обработчики событий attachFormulaBuilderEvents(frm, formula_fields, grouped_fields); attachTriggerEditorEvents(frm, trigger_fields, grouped_trigger_fields); @@ -1185,6 +1081,8 @@ function createModals() { createFormulaBuilderModal(); createTriggerFieldsModal(); createUniversalFieldsModal(); + createTableRowFormulaModal(); + createTableRowFieldSelectorModal(); } // Создание HTML для поля формулы @@ -1217,6 +1115,24 @@ function createFormulaFieldHtml(field, dataAttr) { // Общие функции и утилиты function determineFieldType(fieldName, group, isInTableField) { + // Проверяем, является ли поле ссылкой на строку табличной части + const rowPattern = /^([a-z_]+)_row_(\d+)_([a-z_]+)$/i; + const rowMatch = fieldName.match(rowPattern); + + if (rowMatch) { + const tableName = rowMatch[1]; + const rowIndex = rowMatch[2]; + const fieldNameInRow = rowMatch[3]; + + return { + type: 'table_row', + tableName: tableName, + rowIndex: rowIndex, + fieldName: fieldNameInRow, + fullFieldName: fieldName + }; + } + // Получаем части имени поля // Проверяем существование имен групп "items", "taxes" и т.д. без подчеркивания const tablePrefixes = ["items", "taxes", "packed_items", "payments", "advances", "timesheets", "payment_schedule", "sales_team"]; @@ -1713,42 +1629,42 @@ $('head').append(modalStyles); } function createTriggerFieldsModal() { -if ($("#trigger_fields_modal").length) { - return; -} + if ($("#trigger_fields_modal").length) { + return; + } -const modalHtml = ` -