Здравствуйте!
Задача: Организовать фильтрацию по вложенным записям в древовидном реестре.
Есть грид, который служит только для отображения записей и дерево с одной степенью вложенности.
Записи вида 00001 - корневые
000001-1, 000001-2, 000001-3 - подчиненные
Также в таблице есть поля с идентификаторами ID, ParentID. Для корневых записей ID = ParentID
В общем, все организовано по примеру раздела [Проекты], но в проектах реализован хитрый механизм фильтрации, который позволяет фильтровать по подчиненным записям, с любой вложенности. Вы можете проверить это профильтровав раздел Проекты, по названию подчиненной стадии, предварительно ее создав.
Я подозреваю, что получилось организовать такую хитрую фильтрацию с помощью следующих механизмов:

function InitializeFiltersSchemes() {
        ProjectWorkspace.IsChangeFilterScheme = false;
        ProjectWorkspace.GridFiltersScheme = new Object();
        ProjectWorkspace.GridFiltersScheme.XMLStorage = GetNewXMLStorage();
        ProjectWorkspace.GridFiltersScheme.CustomFilters = GetNewXMLStorage();
        var GridCustomFilters = ProjectWorkspace.GridFiltersScheme.CustomFilters;
        GridCustomFilters.InitRootNode('CustomFilters');
        GridCustomFilters.RootNode.AddChildNode('FiltersData');
        SaveFiltersToScheme(ProjectWorkspace.GridFiltersScheme);
        ProjectWorkspace.GanttFiltersScheme = new Object();
        ProjectWorkspace.GanttFiltersScheme.XMLStorage = GetNewXMLStorage();
        ProjectWorkspace.GanttFiltersScheme.CustomFilters = GetNewXMLStorage();
        var GanttCustomFilters = ProjectWorkspace.GanttFiltersScheme.CustomFilters;
        GanttCustomFilters.InitRootNode('CustomFilters');
        GanttCustomFilters.RootNode.AddChildNode('FiltersData');
        SaveFiltersToScheme(ProjectWorkspace.GanttFiltersScheme);
}

function SaveFiltersToScheme(Scheme) {
        var CustomFiltersNode = Scheme.CustomFilters.RootNode.Items(0);
        CustomFiltersNode.SetAttributeAsBool('ShowForPeriod', chbShowForPeriod.IsChecked, false);
        SetStrAttributeToXMLNode(CustomFiltersNode, 'PeriodType', cbPeriodType.DataField.Value);
        SetStrAttributeToXMLNode(CustomFiltersNode, 'FromDate', dtcFromDate.DataField.Value);
        SetStrAttributeToXMLNode(CustomFiltersNode, 'ToDate', dtcToDate.DataField.Value);
        CustomFiltersNode.SetAttributeAsBool('ShowForOpportunity', chbShowForOpportunity.IsChecked, false);
        SetStrAttributeToXMLNode(CustomFiltersNode, 'Project', edtProject.Value);
        CustomFiltersNode.SetAttributeAsBool('ShowForContact', chbShowForContact.IsChecked, false);
        SetStrAttributeToXMLNode(CustomFiltersNode, 'Contact', edtContact.Value);
        CustomFiltersNode.SetAttributeAsBool('ShowChildren', chbShowChildren.IsChecked, false);
        Scheme.FilterBuilder = GetFilterBuilderXMLDataEx(fbcFilters, Scheme.XMLStorage);
}

function LoadFiltersFromScheme(Scheme) {
        var CustomFiltersNode = Scheme.CustomFilters.RootNode.Items(0);
        chbShowForPeriod.IsChecked = CustomFiltersNode.GetAttributeAsBool('ShowForPeriod', false);
        var DateEnumID = GetStrAttributeFromXMLNode(CustomFiltersNode, 'PeriodType');
        if (IsEmptyValue(DateEnumID)) {
                dtcFromDate.DataField.Value = GetStrAttributeFromXMLNode(CustomFiltersNode, 'FromDate');
                dtcToDate.DataField.Value = GetStrAttributeFromXMLNode(CustomFiltersNode, 'ToDate');
        } else {
                cbPeriodType.DataField.Value = GetStrAttributeFromXMLNode(CustomFiltersNode, 'PeriodType');
        }
        chbShowForOpportunity.IsChecked = CustomFiltersNode.GetAttributeAsBool('ShowForOpportunity', false);
        edtProject.Value = GetStrAttributeFromXMLNode(CustomFiltersNode, 'Project');
        chbShowForContact.IsChecked = CustomFiltersNode.GetAttributeAsBool('ShowForContact', false);
        edtContact.Value = GetStrAttributeFromXMLNode(CustomFiltersNode, 'Contact');
        chbShowChildren.IsChecked = CustomFiltersNode.GetAttributeAsBool('ShowChildren', false);
        fbcFilters.ClearFilter();
        fbcFilters.FiltersBuilder.Deserialize(Scheme.FilterBuilder);
        fbcFilters.Refresh();
        fbcFilters.ApplyFilter();
}

Я так понимаю, что мы должны научить FilterBuilder ходить по вложенным записям, для этого переопределить логику, но как это делается в строчках выше мне не понятно.
Просьба пояснить или дать пример организации корректной фильтрации по древовидному реестру.

Конфигурация Terrasoft 3.3.2.157
Бинарные файлы Terrasoft 3.3.2.268

Нравится

7 комментариев

Здравствуйте.

Уточните, пожалуйста, при фильтрации Вам необходимо чтобы отображалась только(!) подчиненная запись и её родитель, или же родитель этой записи и все подчиненные этому родителю записи?

Здравствуйте Дмитрий,
Необходимо отображать:

"Олейник Дмитрий" написал:родитель этой записи и все подчиненные этому родителю записи

Здравствуйте.

Предлагаю базовый механизм быстрого фильтра не трогать, а добавить какую либо кнопку "Фильтр" в окно древовидного реестра.
На OnClick пропишем примерно следующее:

var Dataset = dlData.Dataset;
Dataset.DisableEvents();
vat FilterText = edtFilterText.Value;
ApplyDatasetFilter(Dataset, 'Text', FilterText, true);
Dataset.Open();
var ParentID = Dataset('ParentID');
if(ParentID != null)
{
Dataset.Close();
EnableAllFilters(Dataset.SelectQuery.Items(0).Filters, false);
ApplyDatasetFilter(Dataset, 'ID', ParentID, true);
Dataset.Open();
}
RefreshDataset(Dataset);
Dataset.EnableEvents();

Здравствуйте Дмитрий,
Спасибо за идею с обходным решением, не проверял его еще на практике.
Но так как есть требование иметь полную функциональность работы с фильтрами (как в разделе проекты) у меня есть более "красивая" идея как реализовать задачу.
По сути когда мы ставим Like фильтр по какому либо из полей (допустим текстовом) в древовидном реестре, Террасофт посылает запрос

...
WHERE(([tbl_ControlPaySection].[Name] LIKE ''%'' + @P1 + ''%''))
...

где @P1 значение введенное в клиенте
И данные запрос нам вернет ID подчинённых записей, но не корневых. Так как корневых записей нет, в гриде мы ничего не увидим.
Обыграть это можно построив запрос вида:

select tbl_ControlPaySection.OffshNumber from  tbl_ControlPaySection  
where tbl_ControlPaySection.name like '% текст %'
or ID in (select ParentID from tbl_ControlPaySection  a
where a.name like '% текст %'
)
order by tbl_ControlPaySection.OffshNumber

Для того что бы в TSAdmin'е делать подобные фильтры, есть функциональность Пользовательских фильтров (User Filter). Построить подобный запрос можно следующим образом (В данном случае у нас будет String User Filter):
1

2

Проблема в том, что когда вызвать этот Пользовательский фильтр в клиенте, Террасофт сформирует запрос:

...
WHERE(([tbl_ControlPaySection].[Name] LIKE ''%'' + @P1 + ''%''))
ORDER BY
	5 ASC',N'@P1 nvarchar(4000)',N'текст с клиента'

т.е он не учитывает логики которая прописана в TSAdmin а просто делает Like по полю, как будто пользовательского фильтра нет.
Если пользовательский фильтр активировать (поставить галочку) ситуация не поменяется.

Конфигурация Terrasoft 3.3.2.157
Бинарные файлы Terrasoft 3.3.2.268

Здравствуйте!

А если включить корневой блок WHERE?

Для полноценной фильтрации (по аналогии с разделом "Проэкты"), после ApplyDatasetFilter по искомому поисковому запросу, Вам необходимо проверить, корневой это узел или нет. В случае если ParentID <> null, необходимо фильтровать датасет по этому ParentID, в ином случае (ParentID = null) фильтрация пройдет корректно.

"Олейник Дмитрий" написал:А если включить корневой блок WHERE?

Ситуация не поменялась

"Олейник Дмитрий" написал:Для полноценной фильтрации (по аналогии с разделом "Проэкты"), после ApplyDatasetFilter по искомому поисковому запросу, Вам необходимо проверить, корневой это узел или нет. В случае если ParentID <> null, необходимо фильтровать датасет по этому ParentID, в ином случае (ParentID = null) фильтрация пройдет корректно.

Данный комментарий к коду

"Олейник Дмитрий" написал:var Dataset = dlData.Dataset;
Dataset.DisableEvents();
vat FilterText = edtFilterText.Value;
ApplyDatasetFilter(Dataset, 'Text', FilterText, true);
Dataset.Open();
var ParentID = Dataset('ParentID');
if(ParentID != null)
{
Dataset.Close();
EnableAllFilters(Dataset.SelectQuery.Items(0).Filters, false);
ApplyDatasetFilter(Dataset, 'ID', ParentID, true);
Dataset.Open();
}
RefreshDataset(Dataset);
Dataset.EnableEvents();

понятен, но как сделать что бы можно было пользоваться FilterBuilder'ом?
Просьба уточнить, как работают пользовательские фильтры (User Filter), в случае строкового фильтра (String User Filter).
Что я делаю не так в пользовательском запросе что он формирует запрос к БД

LEFT OUTER JOIN
	[dbo].[tbl_OffshClients] AS [tbl_OffshClients] ON [tbl_OffshClients].[ID] = [tbl_OffshCompanys].[Clients]
WHERE(([tbl_ControlPaySection].[Name] LIKE ''%'' + @P1 + ''%''))
ORDER BY
	4 DESC',N'@P1 nvarchar(4000)',N'Годовой'

и не учитывает всю логику подзапросов.

Дмитрий, постройте простой SQ с пользовательским фильтром с логикой отчличной от Like фильтрации по полю табл., и вы поймете, срабатывают в принципе пользовательские фильтры. Как я вижу не срабатывают, а должны.

Здравствуйте.

"АльфаКрыса" написал: но как сделать что бы можно было пользоваться FilterBuilder'ом?

Для того чтобы пользоваться Filter Build'ером, необходимо в sq добавить Union таблицы саму на себя, после чего добавить фильтр NotExistInChild, ExistsInPath по аналогии c sq_Project.

Показать все комментарии