Added new trigger

This commit is contained in:
Ali 2025-03-30 19:02:58 +04:00
parent 282ba65f58
commit 36e1402a77
1 changed files with 459 additions and 106 deletions

View File

@ -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 {
@ -226,19 +286,133 @@ 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];
@ -247,7 +421,7 @@ async function calculate_${table_name}_row(frm, cdt, cdn) {
await calculate_universal_fields(frm);
`;
} else {
calcFunction = `
calcFunctionBody = `
function calculate_${table_name}_row(frm, cdt, cdn) {
let row = locals[cdt][cdn];
@ -255,39 +429,98 @@ function calculate_${table_name}_row(frm, cdt, cdn) {
}
// Добавляем расчеты для каждого поля табличной части
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);
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;
});
// Добавляем обработчики для каждого триггерного поля
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`;
child_script += ` "${field}": async function(frm, cdt, cdn) {
// Сначала обновляем значения в текущей строке
await calculate_${table_name}_row(frm, cdt, cdn);
// Затем пересчитываем поля основного документа
${triggerCodes.join('\n ')}
},\n`;
} else {
table_script += ` "${field}": function(frm, cdt, cdn) { calculate_${table_name}_row(frm, cdt, cdn); },\n`;
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);`;
}
}).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,7 +1072,14 @@ function generate_dynamic_fields(frm, doctype, callback) {
return;
}
let fields = r.message;
let formula_fields = r.message;
// Получаем все подходящие поля для триггеров
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;
@ -847,11 +1088,11 @@ function generate_dynamic_fields(frm, doctype, callback) {
let wrapper = $(frm.fields_dict.formula_fields.wrapper);
wrapper.empty();
// Группировка полей
// Группировка полей для формул
let grouped_fields = { "Основные поля": [] };
let table_fieldnames = new Set();
fields.forEach(field => {
formula_fields.forEach(field => {
if (!field.fieldname) return;
let label = field.label ? field.label : "*Нет наименования*";
@ -862,13 +1103,39 @@ function generate_dynamic_fields(frm, doctype, callback) {
if (!grouped_fields[table_name]) {
grouped_fields[table_name] = [];
}
grouped_fields[table_name].push({ fieldname: field.fieldname, label: field_label });
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 = $('<div class="formula-container" style="display:flex;flex-direction:column;align-items:center;justify-content:center;max-width:900px;margin:0 auto;"></div>');
// Создаем модальные окна
@ -902,14 +1169,16 @@ function generate_dynamic_fields(frm, doctype, callback) {
renderUniversalFields(frm);
// Прикрепляем обработчики событий
attachFormulaBuilderEvents(frm, fields, grouped_fields);
attachTriggerEditorEvents(frm, fields, grouped_fields);
attachFormulaBuilderEvents(frm, formula_fields, grouped_fields);
attachTriggerEditorEvents(frm, trigger_fields, grouped_trigger_fields);
frm.refresh();
if (callback) callback();
}
});
}
});
}
// Создание всех модальных окон
function createModals() {
@ -2109,6 +2378,39 @@ function openTriggerFieldsEditor(frm, targetFieldname, currentTriggers, fields,
function populateTriggerFieldsPanel($modal, frm, grouped_fields) {
const $container = $modal.find(".trigger-field-groups-container");
// Добавляем секцию предопределенных событий
const $predefinedEventsGroup = $(`
<div class="trigger-field-group">
<div class="trigger-field-group-title">Предопределенные события</div>
<div class="trigger-field-buttons-container" data-group="predefined-events"></div>
</div>
`);
const $predefinedButtonsContainer = $predefinedEventsGroup.find('.trigger-field-buttons-container');
// Добавляем кнопки для предопределенных событий
const predefinedEvents = [
{ name: "При открытии документа", id: "При открытии документа" }
];
predefinedEvents.forEach(event => {
const $eventBtn = $(`
<button class="btn btn-sm trigger-field-btn draggable-item highlight-event"
data-fieldname="${event.id}"
data-label="${event.name}"
data-group="predefined-events"
draggable="true">
${event.name}
</button>
`);
$predefinedButtonsContainer.append($eventBtn);
});
// Добавляем группу предопределенных событий в начало контейнера
$container.prepend($predefinedEventsGroup);
// Добавляем обычные поля
Object.keys(grouped_fields).forEach(group => {
const $fieldGroup = $(`
<div class="trigger-field-group">
@ -2120,12 +2422,13 @@ function populateTriggerFieldsPanel($modal, frm, grouped_fields) {
const $buttonsContainer = $fieldGroup.find('.trigger-field-buttons-container');
grouped_fields[group].forEach(field => {
// Создаем кнопку поля
// Создаем кнопку поля с информацией о типе поля
const $fieldBtn = $(`
<button class="btn btn-sm trigger-field-btn draggable-item"
data-fieldname="${field.fieldname}"
data-label="${field.label}"
data-group="${group}"
data-fieldtype="${field.fieldtype || ''}"
draggable="true">
${field.label}
</button>
@ -2136,6 +2439,25 @@ function populateTriggerFieldsPanel($modal, frm, grouped_fields) {
$container.append($fieldGroup);
});
// Добавляем стили для выделения предопределенных событий
if (!$("#predefined-events-style").length) {
$("head").append(`
<style id="predefined-events-style">
.highlight-event {
background-color: #f8f1c6 !important;
border-color: #e6d292 !important;
}
.highlight-event:hover {
background-color: #f0e6b1 !important;
}
.trigger-token.highlight-event {
background-color: #f8f1c6;
border-color: #e6d292;
}
</style>
`);
}
}
function addTokenToTriggers(fieldname, $resultContainer) {
@ -2145,7 +2467,11 @@ function addTokenToTriggers(fieldname, $resultContainer) {
return; // Если поле уже добавлено, не добавляем его снова
}
const $token = $(`<span class="trigger-token field" data-value="${fieldname}" contenteditable="false">${fieldname}<span class="token-remove">×</span></span>`);
// Определяем, является ли это предопределенным событием
const isPredefined = isPredefinedEvent(fieldname);
const tokenClass = isPredefined ? "trigger-token field highlight-event" : "trigger-token field";
const $token = $(`<span class="${tokenClass}" data-value="${fieldname}" contenteditable="false">${fieldname}<span class="token-remove">×</span></span>`);
$resultContainer.append($token).append(' ');
// Переносим курсор в конец контейнера
@ -2330,7 +2656,11 @@ function handleTriggerDrop(e, $container) {
return; // Если поле уже добавлено, не добавляем его снова
}
const $newElement = $(`<span class="trigger-token field" data-value="${data.fieldname}" draggable="true" contenteditable="false">${data.fieldname}<span class="token-remove">×</span></span>`);
// Определяем, является ли это предопределенным событием
const isPredefined = data.group === 'predefined-events';
const tokenClass = isPredefined ? "trigger-token field highlight-event" : "trigger-token field";
const $newElement = $(`<span class="${tokenClass}" data-value="${data.fieldname}" draggable="true" contenteditable="false">${data.fieldname}<span class="token-remove">×</span></span>`);
// Вставляем новый элемент
if (position.element) {
@ -2374,6 +2704,7 @@ function handleTriggerDrop(e, $container) {
});
}
} catch (error) {
console.error("❌ Ошибка при обработке drop для триггера:", error);
}
}
@ -2385,7 +2716,11 @@ function parseAndRenderTriggers($container, triggers) {
// Создаем токены для каждого поля
triggerFields.forEach(field => {
const $token = $(`<span class="trigger-token field" data-value="${field}" contenteditable="false">${field}<span class="token-remove">×</span></span>`);
// Определяем, является ли это предопределенным событием
const isPredefined = isPredefinedEvent(field);
const tokenClass = isPredefined ? "trigger-token field highlight-event" : "trigger-token field";
const $token = $(`<span class="${tokenClass}" data-value="${field}" contenteditable="false">${field}<span class="token-remove">×</span></span>`);
$container.append($token).append(' ');
});
@ -3991,3 +4326,21 @@ function processAzerbayjanDateField(docValues) {
// Если это не специальное поле или значение не распознано, возвращаем null
return null;
}
function mapEventToHandler(trigger) {
const eventMap = {
"При открытии документа": "refresh",
"when_document_opens": "refresh",
"document_opens": "refresh"
};
return eventMap[trigger] || trigger;
}
function isPredefinedEvent(trigger) {
const predefinedEvents = [
"При открытии документа"
];
return predefinedEvents.includes(trigger);
}