Представления с карточками¶
В карточке документа AbDocument уже есть несколько полей, связанных со схемой данных, поэтому карточку можно начать использовать. Но есть одно “но”: созданные карточки нигде не будут отображаться, т.к. в системе не определён источник данных для этих карточек – т.е. не создано представление, которое должно быть размещено в рабочем месте.
Метаинформация представления¶
Перейдём на вкладку “Представления” и создадим представление “AbDocuments” следующего вида:

Перейдите во вкладку `Редактор JSON` и вставьте следующий код:
{
"Alias": "AbDocuments",
"Appearance": null,
"Appearances": null,
"AutoWidthRowLimit": null,
"Caption": "Documents",
"Columns": [
{
"Alias": "DocID",
"Appearance": null,
"Caption": null,
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": true,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "DocNumber",
"Appearance": null,
"Caption": "Number",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": "t.Number",
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "DocSubject",
"Appearance": null,
"Caption": "Subject",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "TypeID",
"Appearance": null,
"Caption": null,
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": true,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "TypeName",
"Appearance": null,
"Caption": "Type",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": "t.TypeName",
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "PartnerID",
"Appearance": null,
"Caption": null,
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": true,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String"
},
{
"Alias": "PartnerName",
"Appearance": null,
"Caption": "Partner",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String"
}
],
"ConnectionAlias": null,
"DefaultSortColumns": [
{
"Alias": "DocNumber",
"SortDirection": "Descending"
}
],
"EnableAutoWidth": false,
"ExportDataPageLimit": null,
"Extensions": null,
"GroupingColumn": null,
"MultiSelect": false,
"Overrides": null,
"PageLimit": null,
"Paging": "Always",
"Parameters": [
{
"Alias": "Number",
"AllowedOperands": null,
"AutoCompleteInfo": null,
"Caption": "Number",
"Condition": null,
"DisallowedOperands": null,
"DropDownInfo": null,
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": null,
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "bigint"
},
{
"Alias": "Subject",
"AllowedOperands": null,
"AutoCompleteInfo": null,
"Caption": "Subject",
"Condition": null,
"DisallowedOperands": null,
"DropDownInfo": null,
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": null,
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "nvarchar"
},
{
"Alias": "Type",
"AllowedOperands": null,
"AutoCompleteInfo": {
"ParamAlias": "Name",
"PopupColumns": [
{
"::single_type": "int"
},
1
],
"RefPrefix": "Type",
"ViewAlias": "AbDocumentTypes"
},
"Caption": "Type",
"Condition": null,
"DisallowedOperands": null,
"DropDownInfo": {
"PopupColumns": [
{
"::single_type": "int"
},
1
],
"RefPrefix": null,
"ViewAlias": "AbDocumentTypes"
},
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": "AbDocumentTypes",
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "int"
},
{
"Alias": "Partner",
"AllowedOperands": null,
"AutoCompleteInfo": {
"ParamAlias": "Name",
"PopupColumns": [
{
"::single_type": "int"
},
1
],
"RefPrefix": "Partner",
"ViewAlias": "Partners"
},
"Caption": "Partner",
"Condition": null,
"DisallowedOperands": null,
"DropDownInfo": null,
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": "Partners",
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "uniqueidentifier"
},
{
"Alias": "PartnerName",
"AllowedOperands": null,
"AutoCompleteInfo": null,
"Caption": "Partner name",
"Condition": null,
"DisallowedOperands": null,
"DropDownInfo": null,
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": null,
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "nvarchar"
}
],
"QuickSearchParam": null,
"References": [
{
"CardType": null,
"CardTypeColumn": null,
"ColPrefix": "Doc",
"Condition": null,
"DisplayValueColumn": "DocNumber",
"IsCard": true,
"OpenOnDoubleClick": true,
"RefSection": [
{
"::single_type": "str"
},
"AbDocuments"
]
},
{
"CardType": null,
"CardTypeColumn": null,
"ColPrefix": "Type",
"Condition": null,
"DisplayValueColumn": "TypeName",
"IsCard": false,
"OpenOnDoubleClick": false,
"RefSection": [
{
"::single_type": "str"
},
"AbDocumentTypes"
]
},
{
"CardType": null,
"CardTypeColumn": null,
"ColPrefix": "Partner",
"Condition": null,
"DisplayValueColumn": "PartnerName",
"IsCard": true,
"OpenOnDoubleClick": false,
"RefSection": [
{
"::single_type": "str"
},
"Partners"
]
}
],
"RowCounterVisible": false,
"RowCountSubset": "Count",
"SelectionMode": "Row",
"Subsets": [
{
"Alias": "Types",
"Caption": "By type",
"CaptionColumn": "TypeName",
"Condition": null,
"CountColumn": null,
"HideZeroCount": false,
"Kind": "List",
"RefColumn": "TypeID",
"RefParam": "Type",
"TreeHasChildrenColumn": null,
"TreeRefParam": null
},
{
"Alias": "Count",
"Caption": null,
"CaptionColumn": null,
"Condition": null,
"CountColumn": null,
"HideZeroCount": false,
"Kind": "List",
"RefColumn": null,
"RefParam": null,
"TreeHasChildrenColumn": null,
"TreeRefParam": null
}
],
"TreatAsSingleQuery": false,
"TreeGroup": null,
"TreeGroupDisplayValue": null,
"TreeGroupId": null,
"TreeGroupParentId": null,
"TreeId": null,
"TreeParentId": null
}
Рассмотрим, что указывается в метаинформации:
-
Через
DefaultSortColumnsпредставление сортируется по умолчанию по колонкеDocNumber(номер документа) по убыванию номеров; -
Указываем, что представление выполняется с пейджингом, который пользователь не может отключить
Paging: always, т.к. документов может быть зарегистрировано несколько тысяч; -
Для пейджинга указываем название сабсета для подсчёта строк
RowCountSubset: Count. Это означает, что в представлении задан#subsetс алиасомCount. Такой сабсет выполняет представление как запрос SQL, который был шаблонизирован особым образом и который возвращает единственное значение – общее количество строк в представлении (т.е. сколько всего документов в системе). Это позволяет системе показать в панели пейджинга не только номер текущей страницы, но и общее количество страниц1 / 1;
Important
Если указан сабсет
RowCountSubset, то он будет выполнен каждый раз при запросе данных представления системой. Т.е. если пользователь обновит представление или перейдёт на другую страницу, то сначала будет выполнен запрос для получения данных отображаемой страницы, а затем запрос на получение общего количества строк в представлении. Поэтому в целях оптимизации работы представления с миллионами строк сабсетRowCountSubsetможно не указывать, тогда общее количество страниц не будет рассчитываться. Таким образом, например, оптимизировано представление “Available documents” со списком всех доступных документов. -
Для каждой отображаемой или скрываемой колонки добавляем запись в
Columns. Если её не добавить, и колонка будет возвращаться в запросе SELECT, то она отображается с возвращаемым в запросе именем. Например, колонка “Partner” была бы отображена как “PartnerName”, а колонка “PartnerID” не была бы скрыта. Также вColumnsмы задаём сортировкиSortByдля тех колонок, для которых сортировки актуальны (т.к. они требуют наличия индекса в БД для быстрого отображения при наличии тысяч строк в таблицеAbDocuments); -
Parametersпозволяет задать как числовые параметры фильтрацииType: bigint, так и строковыеType: nvarchar. В значенииTypeуказывается тип SQL без размерности, т.е.nvarchar, а неnvarchar(128). Соответственно можно использовать параметры для указания датыType: dateили даты и времениType: datetime, а также других типов SQL; -
Parametersпозволяет добавлять параметры фильтрации в виде полей с автодополнением по аналогии со ссылочными контролами в карточке. Это сделано для параметра с алиасомType, который можно как вводить с клавиатуры с автодополнениемAutoCompleteInfo, так и выбирать через выпадающий список по кнопке со стрелкой внизDropDownInfo. Поскольку такой параметр выбирает идентификатор типа документа, то в свойствеTypeуказываетсяint;-
В
#autocompleteуказывается:-
Алиас представления с типами документов
View: AbDocumentTypes, и ещё параметр этого представления, в который передаётся текст, который начал вводить пользователь и который надо дополнитьParamAlias: Name; -
Свойство
PopupColumnsперечисляет разделённые пробелами индексы колонок из представленияAbDocumentTypes, которые должны отображаться в выпадающем списке при автодополнении. Представление возвращает колонкиTypeIDиTypeName, а вывести надо колонкуTypeNameс именем типа документа, поэтому указываем колонку с индексом “1” (индекс отсчитывается от нуля); -
Далее нужно задать колонку в результате запроса представления
RefPrefix: Type, идентификатор из которой будет передан в параметр с алиасомTypeнашего представленияAbDocuments; -
Свойство
RefSectionопределяет имя секции, на которую ссылается вводимое значение. Это свойство требуется для работы кнопки с троеточием (которая работает так же, как и в ссылочном контроле карточки);
-
-
DropDownInfoнужен только для небольших справочников, для которых действительно надо вывести все значения через выпадающий список. В свойстве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(Number, t.Number)
#param(Subject, t.Subject)
#param(Type, t.TypeID)
#param(Partner, t.PartnerID)
#param(PartnerName, t.PartnerName)
) t2
#if(Normal) {
inner join AbDocuments t with(nolock) on t.ID = t2.ID
}
#if(PageOffset) { /* (5) */
where t2.rn >= #param(PageOffset) and t2.rn < (#param(PageOffset) + #param(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.
