Представления с карточками¶
В карточке документа 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.