Перейти к содержанию

Представления с карточками

В карточке документа 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
}
  1. Мы видим использование оператора #if, который добавляет своё содержимое в фигурных скобках {...}, если выражение внутри круглых скобок #if(...) выполняется.

  2. Вторые фигурные скобки (самый первый #if в запросе) аналогичны блоку “иначе”, т.е. они добавляются, если условие в круглых скобках не выполняется.

  3. #if(Normal) выполняется (добавляет содержимое первых фигурных скобок), когда запрос выполняется в обычном режиме (без сабсетов).

  4. #if(SubsetAlias) выполняется, если задан сабсет с указанным алиасом. Например, #if(Count) выполняется, когда представление выполняется в режиме сабсета Count.

  5. #if(PageOffset) выполняется, если задан специальный параметр PageOffset с количеством строк, которые надо пропустить для определения первой отображаемой строки на текущей выводимой странице. Параметр задаётся системой, когда представление выполняется в режиме без сабсета #if(Normal) и когда постраничное отображение включено: Paging: always или Paging: optional (когда пользователь может сам переключить с постраничного отображения на полное). В общем случае #if(ParamAlias) выполняется, если задан параметр с указанным алиасом (а алиасы параметров и сабсетов не могут совпадать). Например, #if(Number) выполнится, если задано хотя бы одно значение для параметра “Number” с номером документа.

Выноски:

  1. Мы видим использование оператора #if, который добавляет своё содержимое в фигурных скобках {...}, если выражение внутри круглых скобок #if(...) выполняется.

  2. Вторые фигурные скобки (самый первый #if в запросе) аналогичны блоку “иначе”, т.е. они добавляются, если условие в круглых скобках не выполняется.

  3. #if(Normal) выполняется (добавляет содержимое первых фигурных скобок), когда запрос выполняется в обычном режиме (без сабсетов).

  4. #if(SubsetAlias) выполняется, если задан сабсет с указанным алиасом. Например, #if(Count) выполняется, когда представление выполняется в режиме сабсета Count.

  5. #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.

Back to top