На этом уроке мы познакомимся с объектом DatabaseTree, который отображает данные в виде дерева значений, что позволяет лучше воспринимать иерархическую структуру данных. Для этого создадим форму списка товарно-материальных ценностей (ТМЦ), которые будут разделяться на категории.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Новый список
Для начала создадим простенький список единиц измерений, который пригодится при создании списка ТМЦ.
База данных
Для списка единиц измерения создайте таблицу template.unit с колонками:
unit_id smallint NOT NULL - первичный ключ;
title character varying NOT NULL - наименование;
short_title character varying NOT NULL - сокращенное обозначение;
archive boolean NOT NULL DEFAULT false - признак архивной записи.
Создайте формы для редактирования списка единиц измерения:
Для этого можете воспользоваться паттерном ArchiveList из архива:
Пример заполнение настроек паттерна для списка единиц измерения:
Редактор автоматически создаст формы списка (TemplateUnitList.xml) и карточки сущности (TemplateUnitEdit.xml), а также в серверный xml-файл добавит все необходимые запросы.
После применения паттерна перейдем в xml-файл серверной части и перенесем роль UnitEditRole в группу GuestGroup, а также добавим нужные поля на форму карточки сущности, колонки в таблицу на форме списка и скорректируем запросы.
На главной форме в меню добавьте пункт Списки -> Единицы измерения..., по которому будет открываться новая формаTemplateUnitList.xml.
Дерево значений
Нам необходимо создать форму списка для создания и редактирования новой сущности - товарно-материальных ценностей.
Но в этот раз помимо таблицы товарно-материальных ценностей на форме будет список категорий ценностей, который будет представлен в виде дерева, чтобы иметь возможность разбивать категории на подкатегории.
Должна получиться форма вида:
База данных
Создадим в базе данных таблицы:
template.material_category - категории товарно-материальных ценностей;
CREATE SEQUENCE template.material_category_id_seq;
CREATE TABLE template.material_category
(
material_category_id bigint NOT NULL DEFAULT nextval('template.material_category_id_seq'::regclass),
parent_material_category_id bigint,
title character varying NOT NULL,
archive boolean NOT NULL DEFAULT false,
CONSTRAINT pk_material_category_id PRIMARY KEY (material_category_id),
CONSTRAINT fk_parent_material_category_id FOREIGN KEY (parent_material_category_id)
REFERENCES template.material_category (material_category_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Поле parent_material_category_id будем использовать для хранения идентификатора родительской категории.
CREATE SEQUENCE template.material_id_seq;
CREATE TABLE template.material
(
material_id bigint NOT NULL DEFAULT nextval('template.material_id_seq'::regclass),
material_category_id bigint NOT NULL,
title character varying NOT NULL,
unit_id bigint NOT NULL,
unit_price numeric NOT NULL,
archive boolean NOT NULL DEFAULT false,
CONSTRAINT pk_material_id PRIMARY KEY (material_id),
CONSTRAINT fk_material_category_id FOREIGN KEY (material_category_id)
REFERENCES template.material_category (material_category_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_unit_id FOREIGN KEY (unit_id)
REFERENCES template.unit (unit_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Подготовка формы
Создайте пустую форму TemplateMaterialList.xml с заголовком "Товарно-материальные ценности".
В главном меню на стартовой форме (TemplateStart.xml) добавьте пункт меню Списки -> ТМЦ..., по которому будет открываться новая форма со списком товарно-материальных ценностей.
Перейдем в файл TemplateMaterialList.xml, где в ContentPanel добавим три панели:
MaterialCategoryPanel - левая панель, в которой будет описываться дерево категорий товарно-материальные ценностей. Ей можно задать ширину в 400 пикселей;
MaterialPanel - правая панель, в которой будет описываться таблица товарно-материальных ценностей. Эта панель будет занимать все оставшееся место;
MaterialSeparatePanel - разделитель между панелей.
Дерево значений
Займемся левой частью формы, а именно созданием дерева категорий для товарно-материальных ценностей. В итоге у вас должна получится форма подобного вида:
В панели MaterialCategoryPanel создайте:
Надпись "Категории" (MaterialCategoryLabel), которая будет выступать заголовком для дерева значений;
Дерево категорий (MaterialCategoryDatabaseTree) - объект типа DatabaseTree. Тэг <Items> оставьте пустым - его заполним позже;
Кнопки редактирования дерева категорий (добавить, редактировать и удалить).
Для проверки наличия выделенного узла дерева DatabaseTree будем использовать get-проперти SelectedItemId, которое будем проверять в условии типа IsNotNullCondition:
В командах используем тэг <Multiple>, чтобы иметь возможность открывать несколько экземпляров формы одновременно.
Для отслеживания двойного клика по узлу дерева используйте условие DoubleClickCondition. Создайте <Execution>, который будет отслеживать это условие.
Создайте самостоятельно команду MaterialCategoryDeleteMessageBoxCommand для кнопки удаления записи в дереве категорий.
Конструкции <Execution> на эти команды можете оставить пустыми.
Карточка редактирования узла дерева
Создадим форму для редактирования категории (TemplateMaterialCategoryEdit.xml), используя паттерн EntityForm:
Добавьте на форму объекты ParentMaterialCategoryLabel и ParentMaterialCategoryComboBox, которые будут описывать родительскую категорию. Тэги <ValueList> и <Value> для выпадающего списка оставьте пустыми - их заполним позже.
Таким образом, у вас должна получиться форма вида:
Перейдем в xml-файл серверной части (Template.xml) и создадим запрос для получения списка категорий:
Template.xml
<SqlQuery Name="ParentMaterialCategorySelectSqlQuery">
<Text>
WITH RECURSIVE tree_tmp (material_category_id, parent_material_category_id, title, path) AS (
SELECT material_category_id, parent_material_category_id, title, ARRAY[material_category_id]::bigint[]
FROM parent_mc
UNION ALL
SELECT MC.material_category_id, MC.parent_material_category_id, MC.title, T.path || ARRAY[MC.material_category_id]::bigint[]
FROM
mc_tmp MC
JOIN tree_tmp T ON T.material_category_id = MC.parent_material_category_id
), mc_tmp AS (
SELECT *
FROM template.material_category
WHERE material_category_id IS DISTINCT FROM {MaterialCategoryId} OR {WithChild}
), parent_mc AS (
SELECT *
FROM mc_tmp
WHERE parent_material_category_id IS NULL
)
SELECT
T.material_category_id AS "MaterialCategoryId",
CASE WHEN array_length(T.path, 1) > 1 THEN COALESCE(repeat('—', (array_length(T.path, 1) - 1)), '') || ' ' ELSE '' END || T.title AS "Title"
FROM
tree_tmp T
ORDER BY T.path, T.material_category_id;
</Text>
</SqlQuery>
Этот запрос будем использовать в двух местах: в карточке категории ТМЦ для выбора родительской категории и в карточке ТМЦ для выбора категории товарно-материальной ценности. Поэтому сразу добавим в него параметр {WithChild}, которым будем разделять место использования запроса: из карточки категории будем передавать false, а из карточки ТМЦ - true.
В запросе стоит обратить внимание на условие IS DISTINCT FROM, которое для значений не NULL работает так же, как оператор <>. Однако, если оба сравниваемых значения NULL, результат будет false, и только если одно из значений NULL, возвращается true. Следовательно, если параметр {MaterialCategoryId} будет иметь значение NULL, то условие вернет true.
Таким образом, для карточки категории в параметр {MaterialCategoryId} будем передавать идентификатор редактируемой категории, чтобы исключить ее из списка возможных родительских категорий. Тем самым мы избежим того, что бы в качестве родительской категории была выбрана сама редактируемая категория или ее дочерние категории.
В этом запросе используется вычисление рекурсивного запроса, с помощью указания служебного слова RECURSIVE. Подробнее про рекурсивные запросы можете почитать по ссылке.
Добавьте запрос ParentMaterialCategorySelectSqlQuery в разрешение MaterialCategoryViewSqlQueryPermission, которое создалось автоматически при выполнении паттерна EntityForm.
Скорректируем запрос MaterialCategoryByIdSelectSqlQuery, чтобы он возвращал идентификатор родительской категории:
Template.xml
<SqlQuery Name="MaterialCategoryByIdSelectSqlQuery">
<Text>
SELECT
MC.title AS "Title",
MC.parent_material_category_id AS "ParentMaterialCategoryId"
FROM
template.material_category MC
WHERE
MC.material_category_id = {MaterialCategoryId};
</Text>
</SqlQuery>
Скорректируйте самостоятельно запросы MaterialCategoryInsertSqlQuery и MaterialCategoryUpdateSqlQuery, чтобы они сохраняли значение ParentMaterialCategoryId, переданное с формы.
Вернемся в файл TemplateMaterialCategoryEdit.xml и скорректируем MaterialCategoryPrimaryGetDataConnection:
Обратите внимание, что у MaterialCategoryPrimaryGetDataConnection появился тэг <ManualLoad> - признак определяет, будет ли загрузка данных происходить вместе с загрузкой формы или только после ручного обновления соединения с данными. Значение тэга обратно значению параметра Edit. Когда форма открыта на редактирование (значение параметра Edit = True), значение тэга <ManualLoad> будет False - DataConnection будет обновляться вместе с загрузкой формы. А если форма будет открыта на создание, то значение тэга станет True, и DataConnection будет обновляться только по команде. Таким образом, мы можем исключать лишнее обращение к базе данных, если уверены, что там нет нужных данных.
Самостоятельно измените MaterialCategoryInsertSetDataConnection и MaterialCategoryUpdateSetDataConnection, добавив параметр ParentMaterialCategoryId, в который будет передаваться значение из ParentMaterialCategoryComboBox.
Создадим соединения с данными для загрузки списка родительских категорий:
Как вы помните, на форме TemplateMaterialList.xml в команде MaterialCategoryAddFormShowCommand на дочернюю форму передавался параметр ParentMaterialCategoryId, давайте создадим этот параметр:
TemplateMaterialCategoryEdit.xml
<Parameter Name="ParentMaterialCategoryId" />
Теперь можем доделать ParentMaterialCategoryComboBox, заполнив его тэги <ValueList> и <Value>:
Откройте приложение, проверьте загрузку форм и попробуйте создать несколько категорий, одна из которых не будет иметь родительской категории (например, категория "Бумага"), а остальные категории будут ссылаться на нее. Выпадающий список будет иметь вид:
Отлично! Теперь можем продолжить работать с деревом значений на форме списка.
Запрос для DatabaseTree
Перейдем в xml-файл серверной части (Template.xml) и добавим запрос для получения списка категорий (MaterialCategoryListSelectSqlQuery) и запрос для получения взаимосвязей элементов дерева (MaterialCategoryRelationSelectSqlQuery):
Template.xml
<SqlQuery Name="MaterialCategoryListSelectSqlQuery">
<Text>
SELECT
MC.material_category_id AS "MaterialCategoryId",
MC.title AS "Title"
FROM
template.material_category MC
ORDER BY MC.title, MC.material_category_id;
</Text>
</SqlQuery>
<SqlQuery Name="MaterialCategoryRelationSelectSqlQuery">
<Text>
SELECT
material_category_id AS "MaterialCategoryId",
parent_material_category_id AS "ParentMaterialCategoryId"
FROM
template.material_category;
</Text>
</SqlQuery>
Не забудем добавить их в MaterialCategoryViewSqlQueryPermission.
Вернемся в файл формы списка ТМЦ (TemplateMaterialList.xml) и создадим загружающее соединение с данными:
Обязательный тэг <Items> объекта MaterialCategoryDatabaseTree ожидает соединение с данными с двумя таблицами:
Первая таблица должна описывать линейный список элементов дерева и иметь два поля, соответствующие идентификатору элемента и его отображаемое значение;
Вторая таблица должна описывать взаимосвязи элементов дерева и иметь поля: идентификатор элемента и идентификатор родительского элемента.
Таким образом, в тэге <Items> пропишем следующее значение:
Вызов команды UpdatedTrueValueSetCommand позволит при закрытии формы списка ТМЦ уведомить родительскую форму о наличии изменений.
Запустите приложение и попробуйте добавить пару категорий, чтобы проверить работу <Execution>.
Удаление категории
Сложность удаления категории ТМЦ заключается в том, что мы не можем удалить категорию, если она содержит подкатегории и/или позиции ТМЦ. Для проверки этих условий создадим функции.
Первая будет на проверку наличия вложенных позиций ТМЦ:
CREATE OR REPLACE FUNCTION template.material_category_has_materials(in_material_category_id bigint)
RETURNS boolean AS
$BODY$
BEGIN
IF (EXISTS (SELECT * FROM template.material WHERE material_category_id = in_material_category_id))
THEN
RETURN TRUE;
END IF;
RETURN
bool_or(template.material_category_has_materials(material_category_id))
FROM template.material_category
WHERE parent_material_category_id = in_material_category_id;
END;
$BODY$
LANGUAGE plpgsql;
Вторая будет на попытку удаления категории с проверкой на наличие подкатегорий и вложенных позиций ТМЦ:
CREATE OR REPLACE FUNCTION template.material_category_try_delete(in_material_category_id bigint)
RETURNS character varying AS
$BODY$
DECLARE
_has_child_categories boolean = FALSE;
_has_child_materials boolean = FALSE;
_result character varying;
BEGIN
IF (SELECT NOT EXISTS(SELECT * FROM template.material_category WHERE material_category_id = in_material_category_id))
THEN
RETURN NULL;
END IF;
IF (SELECT used FROM template.is_used('material_category', 'material_category_id', ARRAY[]::text[], ARRAY[]::text[], in_material_category_id))
THEN
_has_child_categories = EXISTS (SELECT * FROM template.material_category WHERE parent_material_category_id = in_material_category_id);
_has_child_materials = template.material_category_has_materials(in_material_category_id);
_result = 'Выбранная категория ТМЦ ' ||
CASE
WHEN _has_child_categories AND _has_child_materials
THEN 'содержит вложенные категории и позиции ТМЦ.'
WHEN _has_child_categories
THEN 'содержит вложенные категории ТМЦ.'
WHEN _has_child_materials
THEN 'содержит вложенные позиции ТМЦ.'
ELSE 'используется в программе.'
END;
RETURN _result;
ELSE
DELETE FROM template.material_category
WHERE material_category_id = in_material_category_id;
RETURN NULL;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
Если мы не можем удалить категорию с подкатегориями и/или вложенными позициями ТМЦ, то будем возвращать сообщение с причиной невозможности удаления и предлагать пользователю сделать каскадное удаление категорий, подкатегорий и вложенных позиций ТМЦ.
Создадим функцию для каскадного удаления:
CREATE OR REPLACE FUNCTION template.material_category_delete_cascade(in_material_category_id bigint)
RETURNS void AS
$BODY$
BEGIN
IF (SELECT NOT EXISTS(SELECT * FROM template.material_category WHERE material_category_id = in_material_category_id)) THEN
RETURN;
END IF;
PERFORM template.material_try_delete(array_agg(material_id))
FROM template.material
WHERE material_category_id = in_material_category_id;
PERFORM template.material_category_delete_cascade(material_category_id)
FROM template.material_category
WHERE parent_material_category_id = in_material_category_id;
IF (SELECT used FROM template.is_used('material_category', 'material_category_id', ARRAY[]::text[], ARRAY[]::text[], in_material_category_id))
THEN
UPDATE template.material_category
SET archive = TRUE
WHERE material_category_id = in_material_category_id AND NOT archive;
ELSE
DELETE FROM template.material_category
WHERE material_category_id = in_material_category_id;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION template.material_try_delete(in_material_id_array bigint[])
RETURNS character varying AS
$BODY$
DECLARE
_material_id bigint;
_deleted_count integer := 0;
_archived_count integer := 0;
_archived_array varchar[];
BEGIN
FOR i IN 1..COALESCE(array_length(in_material_id_array, 1), 0)
LOOP
_material_id = in_material_id_array[i];
raise notice '_material_id: %', _material_id;
IF (SELECT used FROM template.is_used('material', 'material_id', ARRAY[]::text[], ARRAY[]::text[], _material_id))
THEN
UPDATE template.material
SET archive = True
WHERE material_id = _material_id AND NOT archive;
_archived_count = _archived_count + 1;
_archived_array = _archived_array || (SELECT title FROM template.material WHERE material_id = _material_id);
ELSE
DELETE FROM template.material
WHERE material_id = _material_id;
_deleted_count = _deleted_count + 1;
END IF;
END LOOP;
RETURN CASE WHEN _archived_count > 0 THEN
'Удалено ТМЦ: ' || _deleted_count ||
E'.\rПеремещено в архив: ' || _archived_count ||
E'.\r\rСписок ТМЦ, перемещенных в архив:\r' || array_to_string(array_sort(_archived_array), E',\r') ELSE NULL END;
END;
$BODY$
LANGUAGE plpgsql;
Функция template.material_try_delete(bigint[]) возвращает сообщение о количестве удаленных и перемещенных в архив записей, а также наименования ТМЦ, отправленных в архив. Но функция template.material_category_delete_cascade(bigint), которая ее вызывает, никак не обрабатывает возвращаемое сообщение и не передает его в результат SQL-запроса, чтобы можно было получить сообщение на форме и отобразить его пользователю. Вы можете самостоятельно реализовать данную функциональность.
В функции template.material_try_delete(bigint[]) используется функция сортировки массива:
CREATE OR REPLACE FUNCTION public.array_sort(anyarray)
RETURNS anyarray AS
$BODY$
SELECT ARRAY(SELECT unnest($1) ORDER BY 1);
$BODY$
LANGUAGE sql;
Перейдем в серверный xml-файл и создадим запросы на удаление категорий:
Теперь можем заняться правой частью формы и создать сам список товарно-материальных ценностей.
На панель MaterialPanel добавим таблицу с кнопками редактирования. Для этого воспользуйтесь паттерном Table:
Выполним следующие настройки паттерна:
После применения паттерна перейдем в xml-файл серверной части и перенесем роль MaterialEditRole в группу GuestGroup, а также скорректируем текст запроса:
Template.xml
<SqlQuery Name="MaterialSelectSqlQuery">
<Text>
SELECT
M.material_id AS "MaterialId",
M.material_category_id AS "MaterialCategoryId",
M.title AS "Title",
M.unit_id AS "UnitId",
M.unit_price AS "UnitPrice"
FROM
template.material M
ORDER BY M.title;
</Text>
</SqlQuery>
Таким образом форма списка (TemplateMaterialList.xml) примет вид:
Добавим на форму соединение с данными для загрузки списка единиц измерений:
В нем используем запрос ParentMaterialCategorySelectSqlQuery, который писали для карточки редактирования категории ТМЦ.
Самостоятельно создайте все необходимые элементы формы, чтобы форма имела вид:
Если форма открыта на редактирование, то в поле "Категория" (MaterialCategoryComboBox) должна отображаться категория редактируемого ТМЦ, иначе должен подставляться идентификатор из параметра MaterialCategoryId.
Для создания выпадающего списка единиц измерений (UnitComboBox) используйте материал прошлого урока, где мы реализовывали логику редактирования выпадающего списка клиентов и режим выбора на форме списка клиентов. Также для UnitComboBox используйте соединение с данными:
Скорректируйте MaterialInsertSetDataConnection и MaterialUpdateSetDataConnection.
Запустите приложение и попробуйте создать несколько позиций для списка товарно-материальный ценностей с разными категориями.
Как видите, независимо от того, какую категорию выбрали, в списке отображаются все записи.
Реализуйте с помощью SecondaryGetDataConnection фильтрацию данных для таблицы ТМЦ на основе выбранной в дереве DatabaseTree категории.
Самостоятельно
Как вы могли заметить, в таблицах template.material_category и template.material есть колонки archive, которые мы используем в функциях на удаление категорий и ТМЦ.
Ваша задача реализовать на форме списка ТМЦ фильтр архивных и актуальных записей и кнопки для работы с архивом категорий и ТМЦ.
Для этого вам понадобятся функции:
Функция для отправки в архив ветки категорий и ТМЦ, принадлежащих этим категориям
CREATE OR REPLACE FUNCTION template.material_category_archive_child(in_material_category_id bigint)
RETURNS void AS
$BODY$
DECLARE
_archive boolean;
_material_category_id bigint;
BEGIN
_archive = archive FROM template.material_category WHERE material_category_id = in_material_category_id;
IF (NOT _archive) THEN RETURN; END IF;
UPDATE template.material
SET archive = _archive
WHERE material_category_id = in_material_category_id;
FOR _material_category_id IN (SELECT material_category_id FROM template.material_category WHERE parent_material_category_id = in_material_category_id)
LOOP
UPDATE template.material_category
SET archive = _archive
WHERE material_category_id = _material_category_id;
PERFORM template.material_category_archive_child(_material_category_id);
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
Функция восстановления всех родительских категорий у выбранной категории
CREATE OR REPLACE FUNCTION template.material_category_unarchive_parent(in_material_category_id bigint)
RETURNS void AS
$BODY$
DECLARE
_parent_material_category_id bigint;
_archive boolean;
BEGIN
SELECT parent_material_category_id, archive
INTO _parent_material_category_id, _archive
FROM template.material_category
WHERE material_category_id = in_material_category_id;
IF (_archive) THEN RETURN; END IF;
IF (_parent_material_category_id IS NOT NULL)
THEN
UPDATE template.material_category
SET archive = _archive
WHERE material_category_id = _parent_material_category_id;
PERFORM template.material_category_unarchive_parent(_parent_material_category_id);
END IF;
END;
$BODY$
LANGUAGE plpgsql;
Последовательность запросов для архивации категорий ТМЦ:
Template.xml
<SqlQuery Name="MaterialCategoryArchiveSqlQuery">
<Text>
UPDATE template.material_category
SET archive = {Archive}
WHERE material_category_id = {MaterialCategoryId};
SELECT template.material_category_archive_child({MaterialCategoryId})
WHERE {Archive};
SELECT template.material_category_unarchive_parent({MaterialCategoryId})
WHERE NOT {Archive};
</Text>
</SqlQuery>
Отправив категорию в архив, необходимо отправить в архив все дочерние категории и ТМЦ, принадлежащие выбранной категории и дочерним категориям. Восстановив категорию из архива, необходимо восстановить и все родительские категории. Это позволит корректно отображать дерево категорий на форме.
Последовательность запросов для архивации ТМЦ:
Template.xml
<SqlQuery Name="MaterialArchiveSqlQuery">
<Text>
UPDATE template.material
SET archive = {Archive}
WHERE material_id = {MaterialId};
UPDATE template.material_category MC
SET archive = False
FROM template.material M
WHERE
MC.material_category_id = M.material_category_id AND
material_id = {MaterialId} AND
NOT M.archive;
SELECT template.material_category_unarchive_parent(M.material_category_id)
FROM template.material M
WHERE
M.material_id = {MaterialId} AND
NOT M.archive;
</Text>
</SqlQuery>
При восстановлении ТМЦ из архива необходимо восстановить ее категорию и все родительские категории.
Запросы MaterialCategoryArchiveSqlQuery и MaterialArchiveSqlQuery используйте на соответствующих кнопках на форме списка ТМЦ.
Переделаем запрос на построение списка категорий, чтобы в дереве отображался признак архивной записи:
MaterialCategoryListSelectSqlQuery
Template.xml
<SqlQuery Name="MaterialCategoryListSelectSqlQuery">
<Text>
SELECT
MC.material_category_id AS "MaterialCategoryId",
MC.title || CASE WHEN archive THEN ' (арх.)' ELSE '' END AS "Title",
MC.title AS "OriginalTitle",
MC.archive AS "Archive",
MC2.material_category_id NOTNULL AS "ArchiveForFilter"
FROM
template.material_category MC
LEFT JOIN
(
WITH RECURSIVE archive_tree (material_category_id, parent_material_category_id) AS(
SELECT
material_category_id,
parent_material_category_id
FROM
template.material_category MC
WHERE
MC.archive AND NOT EXISTS (SELECT * FROM template.material_category MC2 WHERE MC2.parent_material_category_id = MC.material_category_id) OR
EXISTS (SELECT * FROM template.material M WHERE M.archive AND M.material_category_id = MC.material_category_id)
UNION
SELECT
MC.material_category_id,
MC.parent_material_category_id
FROM
template.material_category MC
JOIN archive_tree T ON (T.parent_material_category_id = MC.material_category_id)
)
SELECT DISTINCT material_category_id FROM archive_tree
) MC2 ON (MC2.material_category_id = MC.material_category_id)
ORDER BY MC.title, MC.material_category_id;
</Text>
</SqlQuery>
В соединение с данными MaterialCategoryPrimaryGetDataConnection для списка категорий добавим фильтр архивных записей:
Для дерева MaterialCategoryDatabaseTree используйте вложенный тэг <Sorted> со значением True, чтобы при изменении фильтра узлы дерева сохраняли сортировку.
Итоги
На уроке мы познакомились с объектом DatabaseTree, который отображает древовидную структуру данных. Также создали пару списков, которые будем использовать в следующем уроке для расширения возможностей карточки заказа.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.