Урок 9. Список категорий
На этом уроке мы познакомимся с объектом 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 - признак архивной записи.
Создайте функцию template.unit_try_delete(smallint).
Форма списка и карточка редактирования
Создайте формы для редактирования списка единиц измерения:

Для этого можете воспользоваться паттерном ArchiveList из архива:
Пример заполнение настроек паттерна для списка единиц измерения:

Редактор автоматически создаст формы списка (TemplateUnitList.xml) и карточки сущности (TemplateUnitEdit.xml), а также в серверный xml-файл добавит все необходимые запросы.
После применения паттерна перейдем в xml-файл серверной части и перенесем роль UnitEditRole в группу GuestGroup, а также добавим нужные поля на форму карточки сущности, колонки в таблицу на форме списка и скорректируем запросы.
На главной форме в меню добавьте пункт Списки -> Единицы измерения..., по которому будет открываться новая форма TemplateUnitList.xml.

Дерево значений
Нам необходимо создать форму списка для создания и редактирования новой сущности - товарно-материальных ценностей.
Но в этот раз помимо таблицы товарно-материальных ценностей на форме будет список категорий ценностей, который будет представлен в виде дерева, чтобы иметь возможность разбивать категории на подкатегории.
Должна получиться форма вида:

База данных
Создадим в базе данных таблицы:
template.material_category - категории товарно-материальных ценностей;
template.material - товарно-материальные ценности.
Поле parent_material_category_id будем использовать для хранения идентификатора родительской категории.
Подготовка формы
Создайте пустую форму 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) и создадим запрос для получения списка категорий:
Этот запрос будем использовать в двух местах: в карточке категории ТМЦ для выбора родительской категории и в карточке ТМЦ для выбора категории товарно-материальной ценности. Поэтому сразу добавим в него параметр {WithChild}, которым будем разделять место использования запроса: из карточки категории будем передавать false, а из карточки ТМЦ - true.
В запросе стоит обратить внимание на условие IS DISTINCT FROM, которое для значений не NULL работает так же, как оператор <>. Однако, если оба сравниваемых значения NULL, результат будет false, и только если одно из значений NULL, возвращается true. Следовательно, если параметр {MaterialCategoryId} будет иметь значение NULL, то условие вернет true.
Таким образом, для карточки категории в параметр {MaterialCategoryId} будем передавать идентификатор редактируемой категории, чтобы исключить ее из списка возможных родительских категорий. Тем самым мы избежим того, что бы в качестве родительской категории была выбрана сама редактируемая категория или ее дочерние категории.
В этом запросе используется вычисление рекурсивного запроса, с помощью указания служебного слова RECURSIVE. Подробнее про рекурсивные запросы можете почитать по ссылке.
Добавьте запрос ParentMaterialCategorySelectSqlQuery в разрешение MaterialCategoryViewSqlQueryPermission, которое создалось автоматически при выполнении паттерна EntityForm.
Скорректируем запрос MaterialCategoryByIdSelectSqlQuery, чтобы он возвращал идентификатор родительской категории:
Скорректируйте самостоятельно запросы MaterialCategoryInsertSqlQuery и MaterialCategoryUpdateSqlQuery, чтобы они сохраняли значение ParentMaterialCategoryId, переданное с формы.
Вернемся в файл TemplateMaterialCategoryEdit.xml и скорректируем MaterialCategoryPrimaryGetDataConnection:
Обратите внимание, что у MaterialCategoryPrimaryGetDataConnection появился тэг <ManualLoad> - признак определяет, будет ли загрузка данных происходить вместе с загрузкой формы или только после ручного обновления соединения с данными. Значение тэга обратно значению параметра Edit. Когда форма открыта на редактирование (значение параметра Edit = True), значение тэга <ManualLoad> будет False - DataConnection будет обновляться вместе с загрузкой формы. А если форма будет открыта на создание, то значение тэга станет True, и DataConnection будет обновляться только по команде. Таким образом, мы можем исключать лишнее обращение к базе данных, если уверены, что там нет нужных данных.
Самостоятельно измените MaterialCategoryInsertSetDataConnection и MaterialCategoryUpdateSetDataConnection, добавив параметр ParentMaterialCategoryId, в который будет передаваться значение из ParentMaterialCategoryComboBox.
Создадим соединения с данными для загрузки списка родительских категорий:
Как вы помните, на форме TemplateMaterialList.xml в команде MaterialCategoryAddFormShowCommand на дочернюю форму передавался параметр ParentMaterialCategoryId, давайте создадим этот параметр:
Теперь можем доделать ParentMaterialCategoryComboBox, заполнив его тэги <ValueList> и <Value>:
Откройте приложение, проверьте загрузку форм и попробуйте создать несколько категорий, одна из которых не будет иметь родительской категории (например, категория "Бумага"), а остальные категории будут ссылаться на нее. Выпадающий список будет иметь вид:

Отлично! Теперь можем продолжить работать с деревом значений на форме списка.
Запрос для DatabaseTree
Перейдем в xml-файл серверной части (Template.xml) и добавим запрос для получения списка категорий (MaterialCategoryListSelectSqlQuery) и запрос для получения взаимосвязей элементов дерева (MaterialCategoryRelationSelectSqlQuery):
Не забудем добавить их в MaterialCategoryViewSqlQueryPermission.
Вернемся в файл формы списка ТМЦ (TemplateMaterialList.xml) и создадим загружающее соединение с данными:
Сразу создайте команду DataConnectionRefreshCommand для его обновления.
Обязательный тэг <Items> объекта MaterialCategoryDatabaseTree ожидает соединение с данными с двумя таблицами:
Первая таблица должна описывать линейный список элементов дерева и иметь два поля, соответствующие идентификатору элемента и его отображаемое значение;
Вторая таблица должна описывать взаимосвязи элементов дерева и иметь поля: идентификатор элемента и идентификатор родительского элемента.
Таким образом, в тэге <Items> пропишем следующее значение:
Откроем форму списка ТМЦ и проверим, что у нас получилось.

Обновление дерева
Создадим команду, в которой через set-проперти SelectedItemId объекта DatabaseTree будем выделять нужный элемент в нашем дереве категорий:
Дополним <Execution> последовательностями команд:
Вызов команды UpdatedTrueValueSetCommand позволит при закрытии формы списка ТМЦ уведомить родительскую форму о наличии изменений.
Запустите приложение и попробуйте добавить пару категорий, чтобы проверить работу <Execution>.
Удаление категории
Сложность удаления категории ТМЦ заключается в том, что мы не можем удалить категорию, если она содержит подкатегории и/или позиции ТМЦ. Для проверки этих условий создадим функции.
Первая будет на проверку наличия вложенных позиций ТМЦ:
Вторая будет на попытку удаления категории с проверкой на наличие подкатегорий и вложенных позиций ТМЦ:
Если мы не можем удалить категорию с подкатегориями и/или вложенными позициями ТМЦ, то будем возвращать сообщение с причиной невозможности удаления и предлагать пользователю сделать каскадное удаление категорий, подкатегорий и вложенных позиций ТМЦ.
Создадим функцию для каскадного удаления:
Создадим функцию template.material_try_delete(bigint[]):
Функция template.material_try_delete(bigint[]) возвращает сообщение о количестве удаленных и перемещенных в архив записей, а также наименования ТМЦ, отправленных в архив. Но функция template.material_category_delete_cascade(bigint), которая ее вызывает, никак не обрабатывает возвращаемое сообщение и не передает его в результат SQL-запроса, чтобы можно было получить сообщение на форме и отобразить его пользователю. Вы можете самостоятельно реализовать данную функциональность.
В функции template.material_try_delete(bigint[]) используется функция сортировки массива:
Перейдем в серверный xml-файл и создадим запросы на удаление категорий:
Добавьте их в MaterialCategoryEditSqlQueryPermission.
Вернемся на форму списка ТМЦ и категорий (TemplateMaterialList.xml) и создадим SetDataConnection:
Добавим команды типа SaveCommand:
Создадим условие IsNullCondition для проверки результата выполнения запроса на удаление категории:
И создадим команду для отображения полученного от сервера сообщения:
Добавим Execution:
Паттерн Table
Теперь можем заняться правой частью формы и создать сам список товарно-материальных ценностей.
На панель MaterialPanel добавим таблицу с кнопками редактирования. Для этого воспользуйтесь паттерном Table:

Выполним следующие настройки паттерна:

После применения паттерна перейдем в xml-файл серверной части и перенесем роль MaterialEditRole в группу GuestGroup, а также скорректируем текст запроса:
Таким образом форма списка (TemplateMaterialList.xml) примет вид:

Добавим на форму соединение с данными для загрузки списка единиц измерений:
Добавим в таблицу MaterialDatabaseTable колонки:
Скорректируем команду MaterialAddFormShowCommand, добавив параметр MaterialCategoryId:
Карточка ТМЦ
Первым делом перейдем в серверный xml-файл и скорректируем запросы:
Редактор с помощью паттерна Table уже создал нужный файл формы (TemplateMaterialEdit.xml). Перейдем в него, чтобы создать объекты интерфейса.
Добавим в параметры формы параметр MaterialCategoryId.
Скорректируем соединение с данными MaterialPrimaryGetDataConnection, добавив необходимые поля:
Создадим соединение с данными MaterialCategoryPrimaryGetDataConnection для получения списка категорий:
В нем используем запрос ParentMaterialCategorySelectSqlQuery, который писали для карточки редактирования категории ТМЦ.
Самостоятельно создайте все необходимые элементы формы, чтобы форма имела вид:

Если форма открыта на редактирование, то в поле "Категория" (MaterialCategoryComboBox) должна отображаться категория редактируемого ТМЦ, иначе должен подставляться идентификатор из параметра MaterialCategoryId.
Для создания выпадающего списка единиц измерений (UnitComboBox) используйте материал прошлого урока, где мы реализовывали логику редактирования выпадающего списка клиентов и режим выбора на форме списка клиентов. Также для UnitComboBox используйте соединение с данными:
Скорректируйте MaterialInsertSetDataConnection и MaterialUpdateSetDataConnection.
Запустите приложение и попробуйте создать несколько позиций для списка товарно-материальный ценностей с разными категориями.

Как видите, независимо от того, какую категорию выбрали, в списке отображаются все записи.
Реализуйте с помощью SecondaryGetDataConnection фильтрацию данных для таблицы ТМЦ на основе выбранной в дереве DatabaseTree категории.

Самостоятельно
Как вы могли заметить, в таблицах template.material_category и template.material есть колонки archive, которые мы используем в функциях на удаление категорий и ТМЦ.
Ваша задача реализовать на форме списка ТМЦ фильтр архивных и актуальных записей и кнопки для работы с архивом категорий и ТМЦ.
Для этого вам понадобятся функции:
Последовательность запросов для архивации категорий ТМЦ:
Отправив категорию в архив, необходимо отправить в архив все дочерние категории и ТМЦ, принадлежащие выбранной категории и дочерним категориям. Восстановив категорию из архива, необходимо восстановить и все родительские категории. Это позволит корректно отображать дерево категорий на форме.
Последовательность запросов для архивации ТМЦ:
При восстановлении ТМЦ из архива необходимо восстановить ее категорию и все родительские категории.
Запросы MaterialCategoryArchiveSqlQuery и MaterialArchiveSqlQuery используйте на соответствующих кнопках на форме списка ТМЦ.
Переделаем запрос на построение списка категорий, чтобы в дереве отображался признак архивной записи:
В соединение с данными MaterialCategoryPrimaryGetDataConnection для списка категорий добавим фильтр архивных записей:
Для дерева MaterialCategoryDatabaseTree используйте вложенный тэг <Sorted> со значением True, чтобы при изменении фильтра узлы дерева сохраняли сортировку.
Итоги
На уроке мы познакомились с объектом DatabaseTree, который отображает древовидную структуру данных. Также создали пару списков, которые будем использовать в следующем уроке для расширения возможностей карточки заказа.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Last updated