fixed bug with universal field, and fixed table fields

This commit is contained in:
Ali 2025-04-05 00:04:27 +04:00
parent 1d7a662901
commit 07e26d1395
2 changed files with 289 additions and 213 deletions

View File

@ -21,15 +21,8 @@ def get_doctype_fields(doctype):
"fieldtype": sub_field.fieldtype "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 return fields
@frappe.whitelist() @frappe.whitelist()

View File

@ -1718,6 +1718,7 @@ function openFormulaBuilder(frm, targetFieldname, isTableField, currentFormula,
// Очистка предыдущих обработчиков и данных // Очистка предыдущих обработчиков и данных
$modal.off('click', '.field-btn'); $modal.off('click', '.field-btn');
$modal.off('click', '.universal-field-btn');
$resultContainer.empty(); $resultContainer.empty();
$modal.find(".field-groups-container").empty(); $modal.find(".field-groups-container").empty();
@ -1756,7 +1757,20 @@ function openFormulaBuilder(frm, targetFieldname, isTableField, currentFormula,
// Обработчики для кнопок универсальных полей // Обработчики для кнопок универсальных полей
$modal.on('click', '.universal-field-btn', function() { $modal.on('click', '.universal-field-btn', function() {
const fieldId = $(this).data('field-id'); const fieldId = $(this).data('field-id');
addTokenToFormula(fieldId, 'universal-field', $resultContainer, isTableField, null, frm); const universalFields = JSON.parse(frm.doc.universal_fields_storage || "{}");
const field = universalFields[fieldId];
if (field) {
const formulaText = `univ(${field.name})`;
const $token = $(`<span class="formula-token field universal-field" data-value="${formulaText}" contenteditable="false" draggable="true">${formulaText}<span class="token-remove">×</span></span>`);
$resultContainer.append($token).append(' ');
// Добавляем обработчики для токенов
addTokenHandlers($resultContainer);
// Переносим курсор в конец контейнера
setEndOfContentEditable($resultContainer[0]);
}
}); });
// Показываем модальное окно // Показываем модальное окно
@ -1911,6 +1925,7 @@ function populateFieldsPanel($modal, frm, grouped_fields, isTableField) {
const $fieldBtn = $(` const $fieldBtn = $(`
<button class="btn btn-sm universal-field-btn draggable-item" <button class="btn btn-sm universal-field-btn draggable-item"
data-field-id="${fieldId}"
data-fieldname="univ(${field.name})" data-fieldname="univ(${field.name})"
data-label="${field.name}" data-label="${field.name}"
data-group="universal-fields" data-group="universal-fields"
@ -1945,7 +1960,7 @@ function populateFieldsPanel($modal, frm, grouped_fields, isTableField) {
$tableRowGroup.find('.select-table-row-field').on('click', function() { $tableRowGroup.find('.select-table-row-field').on('click', function() {
// Определяем целевой контейнер в зависимости от контекста // Определяем целевой контейнер в зависимости от контекста
const targetContainer = $modal.hasClass('trigger-focus') ? const targetContainer = $modal.hasClass('trigger-focus') ?
$modal.find(".table-row-trigger") : $modal.find(".trigger-result") :
$modal.find(".formula-result"); $modal.find(".formula-result");
openTableRowFieldSelector(frm, targetContainer, $modal.hasClass('trigger-focus')); openTableRowFieldSelector(frm, targetContainer, $modal.hasClass('trigger-focus'));
@ -1953,64 +1968,78 @@ function populateFieldsPanel($modal, frm, grouped_fields, isTableField) {
} }
function initDragAndDrop($modal, $resultContainer, isTableField, frm) { function initDragAndDrop($modal, $resultContainer, isTableField, frm) {
// Очищаем предыдущие обработчики // Очищаем предыдущие обработчики
$modal.find('.field-btn, .operator-btn').off('dragstart'); $modal.find('.field-btn, .operator-btn, .universal-field-btn').off('dragstart');
$resultContainer.off('dragover dragleave drop'); $resultContainer.off('dragover dragleave drop');
// Настраиваем перетаскивание существующих токенов // Настраиваем перетаскивание существующих токенов
setupTokenDragging($resultContainer); setupTokenDragging($resultContainer);
// Настраиваем перетаскивание полей из панели // Настраиваем перетаскивание полей из панели
$modal.find('.field-btn').on('dragstart', function(e) { $modal.find('.field-btn').on('dragstart', function(e) {
const fieldname = $(this).data('fieldname'); const fieldname = $(this).data('fieldname');
const label = $(this).data('label'); const label = $(this).data('label');
const group = $(this).data('group'); const group = $(this).data('group');
// Проверяем принадлежность к группе // Проверяем принадлежность к группе
const isTableFieldGroup = group !== "Основные поля"; const isTableFieldGroup = group !== "Основные поля";
const fieldData = { const fieldData = {
type: 'field', type: 'field',
fieldname: fieldname, fieldname: fieldname,
label: label, label: label,
group: group, group: group,
isTableField: isTableFieldGroup isTableField: isTableFieldGroup
}; };
e.originalEvent.dataTransfer.setData('text/plain', JSON.stringify(fieldData)); e.originalEvent.dataTransfer.setData('text/plain', JSON.stringify(fieldData));
}); });
// Настраиваем перетаскивание операторов // Настраиваем перетаскивание универсальных полей
$modal.find('.operator-btn').on('dragstart', function(e) { $modal.find('.universal-field-btn').on('dragstart', function(e) {
const operator = $(this).data('operator'); const fieldId = $(this).data('field-id');
const fieldname = $(this).data('fieldname');
e.originalEvent.dataTransfer.setData('text/plain', JSON.stringify({ const label = $(this).data('label');
type: 'operator',
value: operator e.originalEvent.dataTransfer.setData('text/plain', JSON.stringify({
})); type: 'universal-field',
}); fieldId: fieldId,
fieldname: fieldname,
label: label
}));
});
// Обработчики для целевого контейнера // Настраиваем перетаскивание операторов
$resultContainer.on('dragover', function(e) { $modal.find('.operator-btn').on('dragstart', function(e) {
e.preventDefault(); const operator = $(this).data('operator');
e.stopPropagation();
$(this).addClass('drag-hover'); e.originalEvent.dataTransfer.setData('text/plain', JSON.stringify({
type: 'operator',
updateDropMarker(e, $(this)); value: operator
}); }));
});
$resultContainer.on('dragleave', function(e) { // Обработчики для целевого контейнера
$(this).removeClass('drag-hover'); $resultContainer.on('dragover', function(e) {
$(this).find('.drop-marker').remove(); e.preventDefault();
}); e.stopPropagation();
$(this).addClass('drag-hover');
updateDropMarker(e, $(this));
});
$resultContainer.on('drop', function(e) { $resultContainer.on('dragleave', function(e) {
e.preventDefault(); $(this).removeClass('drag-hover');
e.stopPropagation(); $(this).find('.drop-marker').remove();
$(this).removeClass('drag-hover'); });
$(this).find('.drop-marker').remove();
$resultContainer.on('drop', function(e) {
handleDrop(e, $(this), isTableField, frm); e.preventDefault();
}); e.stopPropagation();
$(this).removeClass('drag-hover');
$(this).find('.drop-marker').remove();
handleDrop(e, $(this), isTableField, frm);
});
} }
function setupTokenDragging($container) { function setupTokenDragging($container) {
@ -2101,7 +2130,14 @@ function handleDrop(e, $container, isTableField, frm) {
let $newElement; let $newElement;
if (data.type === 'universal-field') { if (data.type === 'universal-field') {
$newElement = createUniversalFieldToken(data, frm); // Получаем универсальные поля
const universalFields = JSON.parse(frm.doc.universal_fields_storage || "{}");
const field = universalFields[data.fieldId];
if (field) {
const formulaText = `univ(${field.name})`;
$newElement = $(`<span class="formula-token field universal-field" data-value="${formulaText}" draggable="true" contenteditable="false">${formulaText}<span class="token-remove">×</span></span>`);
}
} else if (data.type === 'field') { } else if (data.type === 'field') {
$newElement = createFieldToken(data, isTableField); $newElement = createFieldToken(data, isTableField);
} else if (data.type === 'operator') { } else if (data.type === 'operator') {
@ -2116,6 +2152,7 @@ function handleDrop(e, $container, isTableField, frm) {
initializeTokenEvents($newElement); initializeTokenEvents($newElement);
} }
} catch (error) { } catch (error) {
console.error("Ошибка при обработке перетаскивания:", error);
} }
} }
@ -5210,160 +5247,206 @@ function initTableRowFormulas(frm) {
renderTableRowFormulas(frm); renderTableRowFormulas(frm);
} }
function populateTableRowFields($modal, frm) {
const $container = $modal.find(".field-groups-container");
// Получаем информацию о табличных частях
const tableDoctypes = {};
frappe.model.with_doctype(frm.doc.target_doctype, function() {
const meta = frappe.get_meta(frm.doc.target_doctype);
if (!meta) return;
meta.fields.forEach(field => {
if (field.fieldtype === 'Table' && field.options) {
tableDoctypes[field.fieldname] = field.options;
}
});
// Для каждой табличной части создаем группу полей строк
Object.keys(tableDoctypes).forEach(tableName => {
const childDoctype = tableDoctypes[tableName];
// Получаем поля дочернего DocType
frappe.model.with_doctype(childDoctype, function() {
const childMeta = frappe.get_meta(childDoctype);
if (!childMeta) return;
// Максимальное количество строк для предпросмотра
const maxRows = 25;
// Создаем группы для каждой строки
for (let rowIndex = 1; rowIndex <= maxRows; rowIndex++) {
const groupName = `${tableName} (Строка ${rowIndex})`;
const $fieldGroup = $(`
<div class="field-group table-row-field-group">
<div class="field-group-title" data-table="${tableName}" data-row="${rowIndex}">${groupName}</div>
<div class="field-buttons-container" data-group="${groupName}" data-table="${tableName}" data-row="${rowIndex}"></div>
</div>
`);
const $buttonsContainer = $fieldGroup.find('.field-buttons-container');
// Добавляем поля для строки
childMeta.fields.forEach(field => {
if (field.fieldname && ['Int', 'Float', 'Currency', 'Data', 'Text', 'Date'].includes(field.fieldtype)) {
const fieldId = `${tableName}_row_${rowIndex}_${field.fieldname}`;
const $fieldBtn = $(`
<button class="btn btn-sm field-btn draggable-item"
data-fieldname="${fieldId}"
data-label="${field.label || field.fieldname}"
data-group="${groupName}"
data-table="${tableName}"
data-row="${rowIndex}"
data-field="${field.fieldname}"
draggable="true">
${field.label || field.fieldname}
</button>
`);
$buttonsContainer.append($fieldBtn);
}
});
// Добавляем группу только если есть хотя бы одно поле
if ($buttonsContainer.children().length > 0) {
// Изначально скрываем группу, будем показывать по клику на заголовок
$buttonsContainer.hide();
$container.append($fieldGroup);
// Добавляем обработчик для отображения/скрытия полей по клику на заголовок
$fieldGroup.find('.field-group-title').on('click', function() {
$(this).siblings('.field-buttons-container').toggle();
});
}
}
});
});
});
}
function populateTableRowTriggerFields($modal, frm) { function populateTableRowTriggerFields($modal, frm) {
const $container = $modal.find(".trigger-field-groups-container"); const $container = $modal.find(".trigger-field-groups-container");
$container.empty();
// Получаем информацию о табличных частях // Добавляем секцию предопределенных событий
const tableDoctypes = {}; const $predefinedEventsGroup = $(`
frappe.model.with_doctype(frm.doc.target_doctype, function() { <div class="trigger-field-group">
const meta = frappe.get_meta(frm.doc.target_doctype); <div class="trigger-field-group-title">Предопределенные события</div>
if (!meta) return; <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>
`);
meta.fields.forEach(field => { $predefinedButtonsContainer.append($eventBtn);
if (field.fieldtype === 'Table' && field.options) {
tableDoctypes[field.fieldname] = field.options;
}
});
// Для каждой табличной части создаем группу полей строк
Object.keys(tableDoctypes).forEach(tableName => {
const childDoctype = tableDoctypes[tableName];
// Получаем поля дочернего DocType
frappe.model.with_doctype(childDoctype, function() {
const childMeta = frappe.get_meta(childDoctype);
if (!childMeta) return;
// Максимальное количество строк для предпросмотра
const maxRows = 25;
// Создаем группы для каждой строки
for (let rowIndex = 1; rowIndex <= maxRows; rowIndex++) {
const groupName = `${tableName} (Строка ${rowIndex})`;
const $fieldGroup = $(`
<div class="trigger-field-group table-row-field-group">
<div class="trigger-field-group-title" data-table="${tableName}" data-row="${rowIndex}">${groupName}</div>
<div class="trigger-field-buttons-container" data-group="${groupName}" data-table="${tableName}" data-row="${rowIndex}"></div>
</div>
`);
const $buttonsContainer = $fieldGroup.find('.trigger-field-buttons-container');
// Добавляем поля для строки
childMeta.fields.forEach(field => {
if (field.fieldname) {
const fieldId = `${tableName}_row_${rowIndex}_${field.fieldname}`;
const $fieldBtn = $(`
<button class="btn btn-sm trigger-field-btn draggable-item"
data-fieldname="${fieldId}"
data-label="${field.label || field.fieldname}"
data-group="${groupName}"
data-table="${tableName}"
data-row="${rowIndex}"
data-field="${field.fieldname}"
draggable="true">
${field.label || field.fieldname}
</button>
`);
$buttonsContainer.append($fieldBtn);
}
});
// Добавляем группу только если есть хотя бы одно поле
if ($buttonsContainer.children().length > 0) {
// Изначально скрываем группу, будем показывать по клику на заголовок
$buttonsContainer.hide();
$container.append($fieldGroup);
// Добавляем обработчик для отображения/скрытия полей по клику на заголовок
$fieldGroup.find('.trigger-field-group-title').on('click', function() {
$(this).siblings('.trigger-field-buttons-container').toggle();
});
}
}
});
});
}); });
// Добавляем группу предопределенных событий в начало контейнера
$container.prepend($predefinedEventsGroup);
// Добавляем секцию универсальных полей, если они есть
if (frm.doc.universal_fields_storage) {
const universalFields = JSON.parse(frm.doc.universal_fields_storage || "{}");
if (Object.keys(universalFields).length > 0) {
const $universalFieldGroup = $(`
<div class="trigger-field-group">
<div class="trigger-field-group-title">Универсальные поля</div>
<div class="trigger-field-buttons-container" data-group="universal-fields"></div>
</div>
`);
const $universalButtonsContainer = $universalFieldGroup.find('.trigger-field-buttons-container');
Object.keys(universalFields).forEach(fieldId => {
const field = universalFields[fieldId];
if (!field || !field.name) return;
const $fieldBtn = $(`
<button class="btn btn-sm trigger-field-btn universal-field-btn draggable-item"
data-fieldname="univ(${field.name})"
data-label="${field.name}"
data-group="universal-fields"
data-field-id="${fieldId}"
draggable="true">
${field.name}
</button>
`);
$universalButtonsContainer.append($fieldBtn);
});
$container.append($universalFieldGroup);
}
}
// Добавляем обычные поля
if (frm.grouped_trigger_fields) {
Object.keys(frm.grouped_trigger_fields).forEach(group => {
// Пропускаем пустые группы
if (!frm.grouped_trigger_fields[group] || frm.grouped_trigger_fields[group].length === 0) return;
const $fieldGroup = $(`
<div class="trigger-field-group">
<div class="trigger-field-group-title">${group}</div>
<div class="trigger-field-buttons-container" data-group="${group}"></div>
</div>
`);
const $buttonsContainer = $fieldGroup.find('.trigger-field-buttons-container');
frm.grouped_trigger_fields[group].forEach(field => {
// Создаем кнопку поля с информацией о типе поля
const isTableField = group !== "Основные поля";
const btnClass = isTableField ? "trigger-field-btn table-field-btn" : "trigger-field-btn";
const $fieldBtn = $(`
<button class="btn btn-sm ${btnClass} draggable-item"
data-fieldname="${field.fieldname}"
data-label="${field.label}"
data-group="${group}"
data-fieldtype="${field.fieldtype || ''}"
draggable="true">
${field.label}
</button>
`);
$buttonsContainer.append($fieldBtn);
});
$container.append($fieldGroup);
});
}
// Добавляем группу для полей строк табличных частей
const $tableRowGroup = $(`
<div class="trigger-field-group">
<div class="trigger-field-group-title">Поля строк табличных частей</div>
<div class="trigger-field-buttons-container">
<button class="btn btn-warning btn-sm select-table-row-trigger-field">
<i class="fa fa-plus"></i> Выбрать поле строки т.ч.
</button>
</div>
</div>
`);
$container.append($tableRowGroup);
// Добавляем обработчик для кнопки выбора поля строки табличной части
$tableRowGroup.find('.select-table-row-trigger-field').on('click', function() {
// Получаем контейнер для триггеров
const $triggerContainer = $modal.find(".trigger-result");
// Открываем селектор для поля строки таблицы
openTableRowFieldSelector(frm, $triggerContainer, true);
});
// Добавляем стили для выделения различных типов триггеров
if (!$("#trigger-field-styles").length) {
$("head").append(`
<style id="trigger-field-styles">
.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;
}
.universal-field-btn {
background-color: #eff8e6 !important;
border-color: #b7d69d !important;
}
.universal-field-btn:hover {
background-color: #deedcf !important;
}
.trigger-token.universal-field {
background-color: #eff8e6;
border-color: #b7d69d;
}
.table-field-btn {
background-color: #e3efff !important;
border-color: #abc9f5 !important;
}
.table-field-btn:hover {
background-color: #d0e5ff !important;
}
.trigger-token.table-field {
background-color: #e3efff;
border-color: #abc9f5;
}
.table-row-field-btn {
background-color: #fff3cd !important;
border-color: #ffeeba !important;
}
.table-row-field-btn:hover {
background-color: #ffeeba !important;
}
.trigger-token.table-row-field {
background-color: #fff3cd;
border-color: #ffeeba;
}
.btn.select-table-row-field,
.btn.select-table-row-trigger-field {
background-color: #fff3cd;
border-color: #ffeeba;
}
.btn.select-table-row-field:hover,
.btn.select-table-row-trigger-field:hover {
background-color: #ffeeba;
border-color: #ffdf7e;
}
</style>
`);
}
} }
function addTableRowFieldsStyles() { function addTableRowFieldsStyles() {