Представления с карточками¶
В карточке документа AbDocument
уже есть несколько полей, связанных со схемой данных, поэтому карточку можно начать использовать. Но есть одно “но”: созданные карточки нигде не будут отображаться, т.к. в системе не определён источник данных для этих карточек – т.е. не создано представление, которое должно быть размещено в рабочем месте.
Метаинформация представления¶
Перейдём на вкладку “Представления” и создадим представление “AbDocuments” следующего вида:
#view(DefaultSortColumn: DocNumber, DefaultSortDirection: desc, Paging: always, RowCountSubset: Count)
#column(Alias: DocID, Hidden: true)
#column(Alias: DocNumber, Caption: Number, SortBy: t.Number)
#column(Alias: DocSubject, Caption: Subject)
#column(Alias: TypeID, Hidden: true)
#column(Alias: TypeName, Caption: Type, SortBy: t.TypeName)
#column(Alias: PartnerID, Hidden: true)
#column(Alias: PartnerName, Caption: Partner)
#param(Alias: Number, Caption: Number, Hidden: false, Type: bigint, Multiple: true)
#param(Alias: Subject, Caption: Subject, Hidden: false, Type: nvarchar, Multiple: true)
#param(Alias: Type, Caption: Type, Hidden: false, Type: int, Multiple: true, RefSection: AbDocumentTypes)
{
#autocomplete(View: AbDocumentTypes, Param: Name, RefPrefix: Type, PopupColumns: 1)
#dropdown(View: AbDocumentTypes, PopupColumns: 1)
}
#param(Alias: Partner, Caption: Partner, Hidden: false, Type: uniqueidentifier, Multiple: true, RefSection: Partners)
{
#autocomplete(View: Partners, Param: Name, RefPrefix: Partner, PopupColumns: 1)
}
#param(Alias: PartnerName, Caption: Partner name, Hidden: false, Type: nvarchar, Multiple: true)
#reference(ColPrefix: Doc, RefSection: AbDocuments, DisplayValueColumn: DocNumber, IsCard: true, OpenOnDoubleClick: true)
#reference(ColPrefix: Type, RefSection: AbDocumentTypes, DisplayValueColumn: TypeName, IsCard: false, OpenOnDoubleClick: false)
#reference(ColPrefix: Partner, RefSection: Partners, DisplayValueColumn: PartnerName, IsCard: true, OpenOnDoubleClick: false)
#subset(Alias: Types, Caption: By type, CaptionColumn: TypeName, RefColumn: TypeID, RefParam: Type)
#subset(Alias: Count)
Рассмотрим, что указывается в метаинформации:
-
Через
#view
представление сортируется по умолчанию по колонкеDocNumber
(номер документа) по убыванию номеров. -
Указываем, что представление выполняется с пейджингом, который пользователь не может отключить
Paging: always
, т.к. документов может быть зарегистрировано несколько тысяч. -
Для пейджинга указываем название сабсета для подсчёта строк
RowCountSubset: Count
. Это означает, что в представлении задан#subset
с алиасомCount
. Такой сабсет выполняет представление как запрос SQL, который был шаблонизирован особым образом и который возвращает единственное значение – общее количество строк в представлении (т.е. сколько всего документов в системе). Это позволяет системе показать в панели пейджинга не только номер текущей страницы, но и общее количество страниц1 / 1
.Important
Если указан сабсет
RowCountSubset
, то он будет выполнен каждый раз при запросе данных представления системой. Т.е. если пользователь обновит представление или перейдёт на другую страницу, то сначала будет выполнен запрос для получения данных отображаемой страницы, а затем запрос на получение общего количества строк в представлении. Поэтому в целях оптимизации работы представления с миллионами строк сабсетRowCountSubset
можно не указывать, тогда общее количество страниц не будет рассчитываться. Таким образом, например, оптимизировано представление “Available documents” со списком всех доступных документов. -
Для каждой отображаемой или скрываемой колонки добавляем запись
#column
. Если её не добавить, и колонка будет возвращаться в запросе SELECT, то она отображается с возвращаемым в запросе именем. Например, колонка “Partner” была бы отображена как “PartnerName”, а колонка “PartnerID” не была бы скрыта. Также в#column
мы задаём сортировкиSortBy
для тех колонок, для которых сортировки актуальны (т.к. они требуют наличия индекса в БД для быстрого отображения при наличии тысяч строк в таблицеAbDocuments
). -
#param
позволяет задать как числовые параметры фильтрацииType: bigint
, так и строковыеType: nvarchar
. В значенииType
указывается тип SQL без размерности, т.е.nvarchar
, а неnvarchar(128)
. Соответственно можно использовать параметры для указания датыType: date
или даты и времениType: datetime
, а также других типов SQL. -
#param
позволяет добавлять параметры фильтрации в виде полей с автодополнением по аналогии со ссылочными контролами в карточке. Это сделано для параметра с алиасомType
, который можно как вводить с клавиатуры с автодополнением#autocomplete
, так и выбирать через выпадающий список по кнопке со стрелкой вниз#dropdown
. Поскольку такой параметр выбирает идентификатор типа документа, то в свойствеType
указываетсяint
.-
В
#autocomplete
указывается:-
Алиас представления с типами документов
View: AbDocumentTypes
, и ещё параметр этого представления, в который передаётся текст, который начал вводить пользователь и который надо дополнитьParam: Name
. -
Далее нужно задать колонку в результате запроса представления
RefColumn: TypeID
, идентификатор из которой будет передан в параметр с алиасомType
нашего представленияAbDocuments
. -
Свойство
PopupColumns
перечисляет разделённые пробелами индексы колонок из представленияAbDocumentTypes
, которые должны отображаться в выпадающем списке при автодополнении. Представление возвращает колонкиTypeID
иTypeName
, а вывести надо колонкуTypeName
с именем типа документа, поэтому указываем колонку с индексом “1” (индекс отсчитывается от нуля). -
Свойство
RefSection
определяет имя секции (или имена секций, разделённые пробелами), на которую ссылается вводимое значение. Это свойство требуется для работы кнопки с троеточием (которая работает так же, как и в ссылочном контроле карточки).
-
-
#dropdown
нужен только для небольших справочников, для которых действительно надо вывести все значения через выпадающий список. В свойствеView
также указывается алиас представления, которое будет выполнено для формирования выпадающего списка (никаких параметров в него не передаётся), а в свойствеPopupColumns
перечисляются индексы колонок представленияAbDocumentTypes
, и это снова колонкаTypeName
(индекс “1”).
-
-
#param
с алиасомPartner
обращается к стандартному справочнику контрагентов из типового решения, данные которого предоставляет представление “Partners” (его можно, конечно же, открыть в редакторе представлений, и изучить его параметры). Здесь не используется#dropdown
, т.к. представление может возвращать тысячи контрагентов, которые не получится вывести через выпадающий список. -
#param
с алиасомPartnerName
позволяет искать не по конкретному контрагенту, выбранному из справочника (т.е. не по идентификатору), а по имени контрагента. Таким образом, например, можно найти все документы, которые ссылаются на контрагента с именем, содержащим какую-то подстроку. -
#reference
с префиксомDoc
(колонкиDocID, DocNumber, DocSubject
) предоставляет ссылку на документ, которую можно использовать, например, чтобы исходящий документ ссылался на входящий документ (для этого в карточке документа потребуется сделать ссылку на документ, т.е. на ту же секциюAbDocuments
). -
#reference
с префиксомType
(TypeID
иTypeName
) предоставляет ссылку из документа на тип документа, который в нём указан. -
#reference
с префиксомPartner
, ссылается на того контрагента, на которого ссылается документ (поскольку каждая строка представления описывает один документ, то каждая строка может ссылаться на контрагента, если он указан в документе). Когда мы добавим представлениеAbDocuments
в рабочее место, то любой пользователь, у которого есть доступ к представлениюAbDocuments
и к рабочему месту Documents, сможет выбрать контрагента по кнопке с троеточием не только из стандартного справочника с контрагентами (в рабочем месте “User”), но и из AbDocuments (если это не работает из TessaAdmin, то вы недавно внесли изменения и надо перезапустить TessaAdmin).
Сабсеты¶
Сабсеты определяют режим выборки представления, т.е. SQL-запрос представления выполняется либо для выборки данных, отображаемых в таблице (без сабсета), либо для определения общего количества строк при постраничном выводе (сабсет, указанный в RowCountSubset
), либо при группировке, вызванной на узле, чтобы отобразить значения как подузлы:
Сабсет по типу документа “Types” выполняется с двумя значениями “Incoming” и “Outgoing” (т.к. ни одного документа с типом “Internal” не создано). Эти значения добавляются в узел с представлением.
Результаты выполнения для представления в режиме сабсета “Types” выглядят так:
При выборе значения для представления AbDocuments
указывается параметр Type
, заданный в свойстве RefParam
сабсета #subset
. В параметр передаётся значение из колонки RefColumn: TypeID
из результатов выполнения представления в режиме сабсета Types
(а там возвращается две колонки: TypeID
с идентификатором и TypeName
с названием). Отображаемое пользователю значение (в дереве и при выборе в сообщении “равен ‘Outgoing’“) определяется по колонке CaptionColumn: TypeName
.
Шаблонизируемый запрос представления¶
Прежде чем подробнее описать сабсеты #subset
, добавим код запроса в поле “Запрос”.
select
#if(Normal) { ///* (1) */
t.ID as DocID,
t.Number as DocNumber,
t.Subject as DocSubject,
t.TypeID,
t.TypeName,
t.PartnerID,
t.PartnerName
} { ///* (2) */
t2.*
}
from
(
select
#if(Normal) { ///* (3) */
t.ID,
row_number() over (order by #order_by) as rn
}
#if(Types) {
distinct t.TypeID, t.TypeName
}
#if(Count) { ///* (4) */
count(*) as cnt
}
from AbDocuments t with(nolock)
where 1 = 1
#param_expr(Number, t.Number)
#param_expr(Subject, t.Subject)
#param_expr(Type, t.TypeID)
#param_expr(Partner, t.PartnerID)
#param_expr(PartnerName, t.PartnerName)
) t2
#if(Normal) {
inner join AbDocuments t with(nolock) on t.ID = t2.ID
}
#if(PageOffset) { ///* (5) */
where t2.rn >= #param_expr(PageOffset) and t2.rn < (#param_expr(PageOffset) + #param_expr(PageLimit))
}
#if(Normal) {
order by t2.rn
}
#if(Types) {
order by t2.TypeName
}
-
Мы видим использование оператора
#if
, который добавляет своё содержимое в фигурных скобках{...}
, если выражение внутри круглых скобок#if(...)
выполняется. -
Вторые фигурные скобки (самый первый
#if
в запросе) аналогичны блоку “иначе”, т.е. они добавляются, если условие в круглых скобках не выполняется. -
#if(Normal)
выполняется (добавляет содержимое первых фигурных скобок), когда запрос выполняется в обычном режиме (без сабсетов). -
#if(SubsetAlias)
выполняется, если задан сабсет с указанным алиасом. Например,#if(Count)
выполняется, когда представление выполняется в режиме сабсета Count. -
#if(PageOffset)
выполняется, если задан специальный параметр PageOffset с количеством строк, которые надо пропустить для определения первой отображаемой строки на текущей выводимой странице. Параметр задаётся системой, когда представление выполняется в режиме без сабсета#if(Normal)
и когда постраничное отображение включено:Paging: always
илиPaging: optional
(когда пользователь может сам переключить с постраничного отображения на полное). В общем случае#if(ParamAlias)
выполняется, если задан параметр с указанным алиасом (а алиасы параметров и сабсетов не могут совпадать). Например,#if(Number)
выполнится, если задано хотя бы одно значение для параметра “Number” с номером документа.
Выноски:
-
Мы видим использование оператора
#if
, который добавляет своё содержимое в фигурных скобках{...}
, если выражение внутри круглых скобок#if(...)
выполняется. -
Вторые фигурные скобки (самый первый
#if
в запросе) аналогичны блоку “иначе”, т.е. они добавляются, если условие в круглых скобках не выполняется. -
#if(Normal)
выполняется (добавляет содержимое первых фигурных скобок), когда запрос выполняется в обычном режиме (без сабсетов). -
#if(SubsetAlias)
выполняется, если задан сабсет с указанным алиасом. Например,#if(Count)
выполняется, когда представление выполняется в режиме сабсета Count. -
#if(PageOffset)
выполняется, если задан специальный параметр PageOffset с количеством строк, которые надо пропустить для определения первой отображаемой строки на текущей выводимой странице. Параметр задаётся системой, когда представление выполняется в режиме без сабсета#if(Normal)
и когда постраничное отображение включено:Paging: always
илиPaging: optional
(когда пользователь может сам переключить с постраничного отображения на полное). В общем случае#if(ParamAlias)
выполняется, если задан параметр с указанным алиасом (а алиасы параметров и сабсетов не могут совпадать). Например,#if(Number)
выполнится, если задано хотя бы одно значение для параметра “Number” с номером документа.
Посмотреть, как выглядит запрос с нужным сабсетом, можно на вкладке “Отладка” в свойстве “Выбранное подмножество”.
Например, так выглядит запрос для сабсета Count
. Он возвращает общее количество строк в таблице с указанными параметрами фильтрации. Зададим параметр Number
(номер документа) как “меньше чем 5”.
DECLARE @NUMBER_1 bigint;
SET @NUMBER_1=5;
DECLARE @CURRENTUSERID_1 uniqueidentifier;
SET @CURRENTUSERID_1='3db19fa0-228a-497f-873a-0250bf0a4ccb';
DECLARE @LOCALE_1 int;
SET @LOCALE_1=9;
select
t2.*
from
(
select
count(*) as cnt from AbDocuments t with(nolock)
where 1 = 1
AND ((t.Number < @NUMBER_1))
) t2
В результате выводится количество:
А при выполнении представления без сабсета получаем такой запрос:
DECLARE @NUMBER_1 bigint;
SET @NUMBER_1=5;
DECLARE @PAGEOFFSET_1 int;
SET @PAGEOFFSET_1=1;
DECLARE @PAGELIMIT_1 int;
SET @PAGELIMIT_1=20;
DECLARE @CURRENTUSERID_1 uniqueidentifier;
SET @CURRENTUSERID_1='3db19fa0-228a-497f-873a-0250bf0a4ccb';
DECLARE @LOCALE_1 int;
SET @LOCALE_1=9;
select t.ID as DocID,
t.Number as DocNumber,
t.Subject as DocSubject,
t.TypeID,
t.TypeName,
t.PartnerID,
t.PartnerName
from
(
select t.ID,
row_number() over (order by t.Number desc ) as rn from AbDocuments t with(nolock)
where 1 = 1
AND ((t.Number < @NUMBER_1))
) t2
inner join AbDocuments t with(nolock) on t.ID = t2.ID
where t2.rn >= @PAGEOFFSET_1 and t2.rn < (@PAGEOFFSET_1 + @PAGELIMIT_1)
order by t2.rn
В запрос система автоматически передаёт параметры пейджинга PageOffset
(номер записи, начиная с которой отображается текущая страница), PageLimit
(количество строк в одной странице), идентификатор текущего пользователя CurrentUser
и код языка локализации для пользователя Locale
. Параметр Number
передаётся, т.к. мы его указали (как если бы его указал пользователь).
Результат запроса выглядит так:
Система скрывает из этого результата колонки с идентификаторами (у которых задано Hidden: true
), и представляет результат пользователю в виде таблицы.
Теперь не забудем указать роль “Все сотрудники”, нажав кнопку “Роли” в правом верхнем углу окна, для назначения доступа к представлению, чтобы все пользователи получили к нему доступ.
Настройка узла рабочего места с сабсетом¶
Сохраним представление и добавим его в рабочее место, выбрав вкладку “Рабочие места” в правой панели навигации, а затем выбрав узел “Documents” и нажав кнопку “Создать - Представление” в панели инструментов сверху. В области “Свойства” выберем значение свойства Представление
как “AbDocuments”. Оставим Режим отображения: Всегда
, чтобы узел был доступен как в главном окне TessaClient, так и при выборе ссылок через кнопку с троеточием.
После этого не забудем перетащить drag&drop представление Documents
из списка справа сверху в область размещения узла слева. Проверить результат можно в режиме предпросмотра, нажав кнопку “Просмотр” в правой верхней части окна.
Для удобства использования давайте укажем настройки для сабсета по типу документа. Для этого нажмём кнопку сабсета в узле дерева и кликнем на сабсет “By type”.
Кликнем по добавленному узлу.
В области справа “Свойства” указываются свойства отображения узла с сабсетом в дереве.
-
Узел можно переименовать, указав ему другой
Заголовок
. -
Узел можно скрыть
Режим отображения: Скрыть от пользователя
, т.е. для конкретного узла “Documents” в рабочем месте “Documents” сабсет “By type” будет недоступен. Не будем этого делать для нашего узла. -
Узел можно добавлять сразу в раскрытом состоянии
Отображать узел: Развернутым
. Эта настройка полезна для удобства использования.
Посмотрим, как настройка Отображать узел: Развернутым
выглядит в действии. Пусть пользователь добавляет сабсет.
И он сразу добавляется в развёрнутом виде:
В то время, как без этой настройки узел сабсета свёрнут. Добавлять сабсет как свёрнутый узел полезно, если представление в режиме этого сабсета может выполняться длительное время, и пользователь может случайно кликнуть не по тому сабсету и попасть на экран загрузки. Если узел сабсета добавляется свёрнутым, то сабсет выполняется при первом раскрытии этого узла (или при обновлении представления кнопкой <F5>
или плиткой на левой боковой панели, когда выбран узел сабсета или один из его подузлов).
Итак, мы настроили представление, из которого можно открывать и искать документы с группировкой по типам документов. Представление добавлено в рабочее место, из которого пользователь сможет увидеть его в TessaClient.