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 = `
-
-
-
-
-
-
-
-
-
Доступные поля:
-
+ const modalHtml = `
+
-
-`;
+ `;
-$('body').append(modalHtml);
+ $('body').append(modalHtml);
}
function createUniversalFieldsModal() {
@@ -1797,187 +1713,243 @@ $(frm.wrapper).on('click', '.open-formula-builder', function() {
}
function openFormulaBuilder(frm, targetFieldname, isTableField, currentFormula, fields, grouped_fields) {
-const $modal = $("#formula_builder_modal");
-const $resultContainer = $modal.find(".formula-result");
+ const $modal = $("#formula_builder_modal");
+ const $resultContainer = $modal.find(".formula-result");
-// Очистка предыдущих обработчиков и данных
-$modal.off('click', '.field-btn');
-$resultContainer.empty();
-$modal.find(".field-groups-container").empty();
+ // Очистка предыдущих обработчиков и данных
+ $modal.off('click', '.field-btn');
+ $resultContainer.empty();
+ $modal.find(".field-groups-container").empty();
-// Заполняем текущую формулу, если она есть
-if (currentFormula) {
- parseAndRenderFormula($resultContainer, currentFormula, isTableField);
-}
+ // Заполняем текущую формулу, если она есть
+ if (currentFormula) {
+ parseAndRenderFormula($resultContainer, currentFormula, isTableField);
+ }
-// Заполняем панель с полями
-populateFieldsPanel($modal, frm, grouped_fields, isTableField);
+ // Заполняем панель с полями
+ populateFieldsPanel($modal, frm, grouped_fields, isTableField);
-// Сохраняем данные о текущем поле
-$modal.data("target-field", targetFieldname);
-$modal.data("is-table-field", isTableField);
+ // Сохраняем данные о текущем поле
+ $modal.data("target-field", targetFieldname);
+ $modal.data("is-table-field", isTableField);
-// Обработчики для кнопок операторов
-$modal.find('.operator-btn').off('click').on('click', function() {
- const operator = $(this).data('operator');
- addTokenToFormula(operator, 'operator', $resultContainer);
-});
+ // Обработчики для кнопок операторов
+ $modal.find('.operator-btn').off('click').on('click', function() {
+ const operator = $(this).data('operator');
+ addTokenToFormula(operator, 'operator', $resultContainer);
+ });
-// Обработчик для кнопки применения формулы
-$modal.find('.apply-formula').off('click').on('click', function() {
- const formula = getFormulaText($resultContainer);
- const $targetInput = $(frm.wrapper).find(`.formula-input[data-fieldname="${targetFieldname}"]`);
- $targetInput.val(formula);
- $modal.modal('hide');
-});
+ // Обработчик для кнопки применения формулы
+ $modal.find('.apply-formula').off('click').on('click', function() {
+ const formula = getFormulaText($resultContainer);
+ const $targetInput = $(frm.wrapper).find(`.formula-input[data-fieldname="${targetFieldname}"]`);
+ $targetInput.val(formula);
+ $modal.modal('hide');
+ });
-// Обработчики для кнопок полей - используем делегирование событий
-$modal.on('click', '.field-btn', function() {
- const fieldname = $(this).data('fieldname');
- addTokenToFormula(fieldname, 'field', $resultContainer, isTableField, $(this).data('group'));
-});
+ // Обработчики для кнопок полей - используем делегирование событий
+ $modal.on('click', '.field-btn', function() {
+ const fieldname = $(this).data('fieldname');
+ addTokenToFormula(fieldname, 'field', $resultContainer, isTableField, $(this).data('group'));
+ });
-// Обработчики для кнопок универсальных полей
-$modal.on('click', '.universal-field-btn', function() {
- const fieldId = $(this).data('field-id');
- addTokenToFormula(fieldId, 'universal-field', $resultContainer, isTableField, null, frm);
-});
+ // Обработчики для кнопок универсальных полей
+ $modal.on('click', '.universal-field-btn', function() {
+ const fieldId = $(this).data('field-id');
+ addTokenToFormula(fieldId, 'universal-field', $resultContainer, isTableField, null, frm);
+ });
-// Показываем модальное окно
-$modal.modal('show');
+ // Показываем модальное окно
+ $modal.modal('show');
-// Инициализируем перетаскивание
-initDragAndDrop($modal, $resultContainer, isTableField, frm);
+ // Инициализируем перетаскивание
+ initDragAndDrop($modal, $resultContainer, isTableField, frm);
-// Настраиваем редактируемый контейнер
-setupEditableContainer($resultContainer);
+ // Настраиваем редактируемый контейнер
+ setupEditableContainer($resultContainer);
-// Фокусируемся на редактируемом поле
-$resultContainer.focus();
+ // Фокусируемся на редактируемом поле
+ $resultContainer.focus();
}
function addTokenToFormula(token, type, $resultContainer, isTableField = false, group = null, frm = null) {
-if (type === 'field') {
- let formulaText;
-
- // Определяем тип поля и формулу
- const fieldInfo = determineFieldType(token, group, isTableField);
-
- if (fieldInfo.type === 'row') {
- formulaText = `row(${fieldInfo.fieldName})`;
- } else if (fieldInfo.type === 'sum') {
- let tableName = fieldInfo.tableName || token.match(/^([a-z_]+)/)[1];
- let fieldWithoutPrefix = token.substring(tableName.length);
- if (fieldWithoutPrefix.startsWith('_')) {
- fieldWithoutPrefix = fieldWithoutPrefix.substring(1);
+ if (type === 'field') {
+ let formulaText;
+
+ // Определяем тип поля и формулу
+ const fieldInfo = determineFieldType(token, group, isTableField);
+
+ if (fieldInfo.type === 'table_row') {
+ // Для полей конкретных строк таблиц
+ formulaText = `val(${fieldInfo.fullFieldName})`;
+ } else if (fieldInfo.type === 'row') {
+ formulaText = `row(${fieldInfo.fieldName})`;
+ } else if (fieldInfo.type === 'sum') {
+ let tableName = fieldInfo.tableName || token.match(/^([a-z_]+)/)[1];
+ let fieldWithoutPrefix = token.substring(tableName.length);
+ if (fieldWithoutPrefix.startsWith('_')) {
+ fieldWithoutPrefix = fieldWithoutPrefix.substring(1);
+ }
+
+ formulaText = `sum(${tableName}_${fieldWithoutPrefix})`;
+ } else {
+ formulaText = `val(${fieldInfo.fieldName})`;
}
- formulaText = `sum(${tableName}_${fieldWithoutPrefix})`;
- } else {
- formulaText = `val(${fieldInfo.fieldName})`;
- }
-
- const $token = $(`
${formulaText}×`);
- $resultContainer.append($token).append(' ');
-} else if (type === 'operator') {
- const $token = $(`
${token}×`);
- $resultContainer.append($token).append(' ');
-} else if (type === 'universal-field') {
- // Обработка для универсальных полей
- const universalFields = JSON.parse(frm.doc.universal_fields_storage || "{}");
- const field = universalFields[token];
- if (field) {
- const formulaText = `univ(${field.name})`;
- const $token = $(`
${formulaText}×`);
+ const $token = $(`
${formulaText}×`);
$resultContainer.append($token).append(' ');
+ } else if (type === 'operator') {
+ const $token = $(`
${token}×`);
+ $resultContainer.append($token).append(' ');
+ } else if (type === 'universal-field') {
+ // Обработка для универсальных полей
+ const universalFields = JSON.parse(frm.doc.universal_fields_storage || "{}");
+ const field = universalFields[token];
+ if (field) {
+ const formulaText = `univ(${field.name})`;
+ const $token = $(`
${formulaText}×`);
+ $resultContainer.append($token).append(' ');
+ }
}
-}
-// Переносим курсор в конец контейнера
-setEndOfContentEditable($resultContainer[0]);
+ // Переносим курсор в конец контейнера
+ setEndOfContentEditable($resultContainer[0]);
-// Добавляем обработчики для токенов
-addTokenHandlers($resultContainer);
+ // Добавляем обработчики для токенов
+ addTokenHandlers($resultContainer);
}
function populateFieldsPanel($modal, frm, grouped_fields, isTableField) {
-const $container = $modal.find(".field-groups-container");
-
-Object.keys(grouped_fields).forEach(group => {
- const $fieldGroup = $(`
+ const $container = $modal.find(".field-groups-container");
+ $container.empty();
+
+ // Добавляем секцию предопределенных событий
+ const $predefinedEventsGroup = $(`
-
${group}
-
+
Предопределенные события
+
`);
- const $buttonsContainer = $fieldGroup.find('.field-buttons-container');
+ const $predefinedButtonsContainer = $predefinedEventsGroup.find('.field-buttons-container');
- grouped_fields[group].forEach(field => {
- // Определяем, является ли это поле таблицы или нет
- const isField = group === "Основные поля";
-
- // Создаем кнопку поля с правильными атрибутами
- const $fieldBtn = $(`
-