Lists

Este documento descreve o mecanismo de listagens disponível para as aplicações Elevation 300.

Modelação

Services Model

No services model é definido o artefacto ListOperation. Este artefacto é responsável por definir a fonte de dados que dá suporte a lista.

List Operation

List Operation

O artefacto list operation no modelo de serviços é adicionado através da toolbox.

A list operation necessita de ter a associação ao service correspondente.

É através deste serviço que a entidade principal da lista vai ser escolhida.

Definição de colunas (atributos)

Select Attributes

A lista poderá conter atributos que estejam relacionados com a entidade principal. As colunas poderão ser adicionadas através do menu de contexto: “List Operations” -> “Select Attributes”.

Definição de parâmetros

Select Parameters

Os parâmetros da lista poderão ser adicionados através do menu de contexto da “Add” -> “Normal Attribute Reference”.

Para os parâmetros da lista, estão disponíveis as seguintes propriedades: - Entity Attribute - Read-only - Required - Type (Date, Boolean, ValueList, DateTime, Decimal, LongText, Number, Percentage, ShortText,Text, Time) - Comment - Caption - Name - Implicit Validation Disabled - Validation Rules O tipo de atributos é igual ao definido para esse mesmo atributo na entidade ou na lookup.

Os parâmetros tem de ter um tipo definido e podem ter valor por omissão.

Parameters

Definição de filtro

A ListOperation expõe a propriedade Filter que é utilizada para filtrar o conteúdo dos dados da lista.

Este filtro é aplicado diretamente através da cláusula WHERE na query SQL de obtenção de dados, por isso, tem de ser uma expressão SQL válida.

Dentro deste filtro poderão ser utilizados os nomes dos parâmetros e os nomes das colunas para definir condições.

A utilização de parâmetros tem de ter o prefixo “@” e os nomes das colunas podem ser usados diretamente. Por exemplo:

  • Parâmetro: “StartDate” (utilização “@StartDate”)
  • Coluna: “DocumentDate” (utilização “DocumentDate”)

Por exemplo, para definir uma lista de faturas simplificadas com 3 parâmetros (Customer, StartDate e FromDate), utilizaríamos a seguinte expressão:

DocumentName = 'FS' AND 
(PartyKey LIKE ISNULL(@Customer + '%', '%') AND 
DocumentDate >= ISNULL(@StartDate, DATETIMEFROMPARTS(1753,1,1,0,0,0,0)) AND
DocumentDate <= ISNULL(@EndDate, DATETIMEFROMPARTS(9999,12,31,23,59,59,997)))

Neste exemplo podemos também ver a utilização direta dos nomes das colunas: DocumentName, DocumentDate e PartyKey.

Presentation Model

No presentation model são definidos os artefactos ListOperation e ListView.

O ListOperation é utilizado para referenciar o ListOperation, correspondente, do modelo de serviços.

O ListView define as propriedades visuais da lista.

List Operation

List Operation

A ListOperation faz o mapeamento para o serviço, define o título da lista (prop. Caption) e, através das CreateActions e ViewServiceOperation define quais as ações de criação e edição associadas aos elementos da lista e qual é a listview utilizada para mostrar a lista.

A ListOperation do tipo EntityManagment tem de ter sempre associada pelo menos uma CreateAction e do tipo InsertEntity e caso a ViewActionType seja do tipo “Default” uma ViewServiceOperation do tipo DisplayAndEditEntity.

List View

List View

A list view é um espelho da list operation do modelo de serviços, mas neste artefacto configuram-se apenas as propriedades visuais.

Esta lista necessita de estar sempre sincronizada com o listoperation do modelo de serviços.

A propriedade “Caption”, nos parâmetros e colunas, define o texto que é apresentado na aplicação.

A propriedade “IsVisible”, nos parâmetros e colunas, define se o parâmetro ou coluna são escondidos ou mostrados ao utilizador.

No caso dos parâmetros, só é possível esconder se houver um valor por omissão definido na list operation, do modelo de serviços, correspondente.

Modelar drill down em listas Custom

Nas propriedades das colunas de uma lista custom é possível definir que uma coluna é do tipo drill down.

List View

Quando se escolhe um atributo sendo drill down é necessário definir para que operação de edição/lista pretendemos navegar.

Caso se escolha uma lista, temos a possibilidade de definir a query string que será incluída na navegação. Os parâmetros que se escreverem com {...} vão ser substituídos pelo valor que exista na linha.

Se escolhermos uma navegação para uma view de edição devemos escolher o atributo para o drill down column.

Ordenação e Agrupamento por predefinição

List View Properties

Nas propriedades das colunas é possível definir que a lista será visualizada com agrupamento e ordenação por omissão por uma ou mais colunas.

Aplicação de uma formatação standard a uma coluna

List View Properties Formatting

Nas propriedades de uma coluna é possível definir que esta é apresentada na lista com uma determinada formatação standard.

Para isso estão presentes duas propriedades: uma indicando o tipo de formatação e outra indicando uma coluna do tipo Boolean de referência para a aplicação da formatação.

Caso esta última propriedade esteja definida, a formatação só é aplicada para linhas cujo valor nessa coluna seja True.

A aplicação de uma formatação standard não está disponível para colunas do tipo Boolean nem para colunas com a propriedade Style definida (p.ex. Moneys).

Implementação

O ficheiro .QueriesSchema.tt gera o .sql necessário para que as listas sejam executadas pela aplicação, conforme o que foi modelado no modelo de serviços e de apresentação como é mostrado na secção anterior.

Este ficheiro .tt está incluído em todos os módulos, na pasta de geração de scripts para a base de dados organization.

No modulo query builder da FW, o motor que faz a montagem do sql e parametros correspondentes, é passada ao servidor de base de dados, com uma query sql parametrizada, o que permite que uma maior proteção contra SQL-Injection.

Definições da lista

A definição da lista é guardada em base de dados na tabela do módulo do query builder QueryConfigurations.

Query Definition

A definição da query segue o schema do elevation 210, mas tenta seguir uma abordagem mais simples, por exemplo, sem informação de links ou operadores nos parâmetros.

Nesta query definition está serializada toda a informação necessária para poder visualizar a lista.

Podemos ver no exemplo abaixo a informação necessária para definir a lista de faturas simplificadas.

<Query
    xmlns="http://www.primaverabss.com/reporting/querymodelxaml"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Name="SimpleInvoices"
    Description="RES_ListView_SimpleInvoices_Caption|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256"
    User=""
    ListServiceName="SimpleInvoices"
    SearchPlaceHolderResourceKey="RES_ListView_SimpleInvoices_SearchPlaceHolder|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256"
    HasSearch="True"
    Principal="Sales/Invoice"
    Perspective="Sales/Invoices">
    <Query.Results>
...... 
<Column IsMainDrillDown="True" Name="NaturalKey" Visible="True"        ResourceKey="RES_ListView_SimpleInvoices_Column_Source_NaturalKey|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" PresentationDataType="Text" CanSort="True" CanGroup="True">
  <Column.Expression>
    <AttributeExpression Attribute="NaturalKey"/>
  </Column.Expression>
</Column>
......
</Query.Results>
<Query.Filters>
  <FilterItem IsPerspectiveFilter="false">
    <FilterItem.Condition>
      <Predicate Type="All">
        <Predicate.Conditions>
          <ConditionCollection Capacity="3">
            <ExpressionCondition>
              <ExpressionCondition.ValueExpr>
                <ParameterExpression Name="Customer">
                  <ParameterExpression.Parameter>
                    <Parameter Name="Customer" Required="True" DataType="ColumnFilter" ResourceKey="RES_ListView_SimpleInvoices_Parameter_Customer|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" ReportVisible="True" Value="">
                      <Parameter.Descriptor>
                        <ParameterColumnList Column="PartyKey" Table="[Patterns].[Parties]" />
                      </Parameter.Descriptor>
                    </Parameter>
                  </ParameterExpression.Parameter>
                </ParameterExpression>
              </ExpressionCondition.ValueExpr>
            </ExpressionCondition>
......
          </ConditionCollection>
        </Predicate.Conditions>
      </Predicate>
    </FilterItem.Condition>
  </FilterItem>
</Query.Filters>
<Query.Actions>
  <QueryActions>
    <QueryActions.Create>
    <QueryActionCollection Capacity="2">
      <QueryAction IsDefault="true" Name="CreateSimpleInvoice" ResourceKey="RES_ListOperation_SimpleInvoices_Action_CreateSimpleInvoice|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" Uri="#/{tenant}/{organization}/sales/invoices/default/createsimpleinvoice"/>
      <QueryAction Name="CreateInvoice" ResourceKey="RES_ListOperation_SimpleInvoices_Action_CreateInvoice|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" Uri="#/{tenant}/{organization}/sales/invoices/default/createinvoice"/>
    </QueryActionCollection>
    </QueryActions.Create>
    <QueryActions.Update>
    <UpdateQueryAction Name="ViewAction" Uri="#/{tenant}/{organization}/sales/invoices/default/editsimpleinvoice?id={id}"/>
    </QueryActions.Update>
  </QueryActions>
</Query.Actions></Query>

Colunas de drill-down

A coluna marcada com IsMainDrillDown=”true” é utilizada na lista para navegar para um registo específico. Este atributo é definido no modelo de apresentação, disponível através das propriedades das colunas da lista. Por omissão, serão marcadas como colunas de drill-down as colunas que façam parte da chave da entidade e que não sejam do tipo View.

Para todas as linhas da lista, os valores correspondentes a esta coluna serão links que navegam para a view de edição do registo selecionado.

Ações - QueryActions

Dentro da tag QueryActions estão contidas as operações indicadas em modelação para as ações de create e update.

A primeira ação de Create que o modelador definir será marcada com IsDefault=”true”, para que possa ser distinguida como ação principal de criação na View da lista.

Todas as outras ações de Create aparecerão também disponíveis como ações secundárias.

Runtime Definition

O runtime definition é utilizado para validação de parâmetros e de colunas de ordenação e agrupamento. É definido no formato JSON.

{
  "Module": "Sales",
  "Entity": "Invoices",
  "QueryKey": "SimpleInvoices",
  "ListServiceName": "SimpleInvoices",
  "Parameters": [
    {
      "Name": "Customer",
      "Type": "ColumnFilter"
    },
.......
  ],
  "Columns": [
    {
      "Name": "DocumentName",
      "Type": "ShortText"
    },
.......
  ],
}

O objeto JSON contem as propriedades:

  • Module: módulo da lista
  • Entity: entidade
  • QueryKey: nome da lista
  • ListServiceName: nome do serviço que dá suporta a lista
  • Parameters_ lista de parâmetros que para parâmetro contêm:
    • Name: nome do parâmetro
    • Type:
      • ColumnFilter: parâmetro que tem preenchimento assistido
      • DataFilter”: parâmetro com tipo de dados normal, definido na propriedade DataType.
    • DataType: tipo de dados do parâmetro
  • Columns: colunas da lista
    • Name: nome da coluna
    • Tipo de dados da coluna

Fonte de dados – TVF

A fonte de dados é resultado de uma Table-Valued Function (TVF).

Esta TVF é gerada a partir do modelo de serviços, mais concretamente a partir da definição da List Service Operation.

Parâmetros

No caso de uma list operation do tipo Entity Selection, a TVF gerada apresenta sempre pelo menos dois parâmetros:

  • SearchTerm: termo usado para pesquisa
  • User: indica o utilizador que está a requerer os dados

A estes são adicionados (caso existam) os parâmetros que tenham sido definidos na list service operation.

Dados

No caso de uma list operation do tipo Entity Selection:

  • A lista inclui sempre (de forma automática) a coluna da NaturalKey da entidade a ser selecionada com o nome fixo [Source_NaturalKey].
  • A coluna com a propriedade definida como sendo a entity description (caso exista) também está sempre incluída na lista. Caso tenha sido incluída pelo modelador usa o nome definido caso contrário é incluída automaticamente com o nome [Source_Description].

Os nomes das colunas usados na definição são usados como alias das colunas.

Os joins necessários são gerados sobre a forma de um select aninhado (SRC), para que seja possível manejar a lista como se de um select simples de tratasse.

É ainda neste select aninhado que se incluem, caso existam, os filtros a todos os searchable fields incluídos na lista (atributos lookup fields marcados como search no modelo de entidades).

Filtros de coluna

Os dados são obtidos invocando a TVF gerada que suporta a lista.

Um dos parametros da TVF é precisamente o texto a pesquisar com a notação do comando LIKE em SQL, em que o caracter % é o wild card indicando qual é o critério de pesquisa (starts with, contains, ends with).

A pesquisa do texto na TVF é feita sobre todos os lookup attributes definidos, em que o critério é especificado com o caracter %.

No caso da pesquisa numa determinada coluna, como texto da pesquisa na TVF é passado o % para devolver todas as entradas, sendo depois aplicado o filtro especifico para a coluna.

Filtro

Conforme apresentado anteriormente é possível definir um filtro na modelação da list service operation. Caso exista, esse filtro é adicionado à TVF.

Por omissão, é possível pesquisar na lista por todas a colunas que façam parte da chave da entidade.

De seguida é apresentado um exemplo de uma TVF gerada para uma lista de faturas simplificadas:

--- [Sales].[Invoices_SimpleInvoicesQuery] TVF

IF EXISTS ( SELECT  1
            FROM    Information_schema.Routines
            WHERE   Specific_schema = 'Sales' AND 
                    specific_name = 'Invoices_SimpleInvoicesQuery'
                    AND Routine_Type = 'FUNCTION' )
    DROP Function [Sales].[Invoices_SimpleInvoicesQuery]
GO

CREATE Function [Sales].[Invoices_SimpleInvoicesQuery]
(
    @searchTerm nvarchar(100) = '%',
    @user nvarchar(100) = NULL,
    @customer nvarchar(20) = NULL,
    @startDate datetime = NULL,
    @endDate datetime = NULL
)
RETURNS TABLE
AS
RETURN
(
        SELECT
            [Id], 
                   [Source_NaturalKey], 
            [PartyKey],
            [BuyerCustomerPartyName],
            [DocumentDate],
            [PayableAmount],
            [DocumentName],
            [Description],
            [SeriesNumber],
            [SerieKey]
        FROM (
                SELECT
                    [T1].[Id], 
                    [T1].[NaturalKey] AS [Source_NaturalKey], 
                    [T2].[PartyKey] AS [PartyKey],
                    [T1].[BuyerCustomerPartyName] AS [BuyerCustomerPartyName],
                    [T1].[DocumentDate] AS [DocumentDate],
                    [T1].[PayableAmount] AS [PayableAmount],
                    [T3].[DocumentName] AS [DocumentName],
                    [T4].[Description] AS [Description],
                    [T1].[SeriesNumber] AS [SeriesNumber],
                    [T5].[SerieKey] AS [SerieKey]
                FROM
                    [Sales].[Invoices] [T1]
                LEFT JOIN 
                    [Patterns].[Parties] [T2] ON [T2].[Id] = [T1].[BuyerCustomerPartyId]
                LEFT JOIN 
                    [Sales].[InvoiceTypes] [T3] ON [T3].[Id] = [T1].[DocumentTypeId]
                LEFT JOIN 
                    [Patterns].[PaymentTerms] [T4] ON [T4].[Id] = [T1].[PaymentTermId]
                LEFT JOIN 
                    [Patterns].[Series] [T5] ON [T5].[Id] = [T1].[SerieId]
                WHERE 
                    [T1].[IsDeleted] = 0 AND
                    ([T1].[NaturalKey] LIKE @searchTerm OR [T1].[BuyerCustomerPartyName] LIKE @searchTerm)
            ) SRC
        WHERE 
            PartyKey LIKE ISNULL(@Customer + '%', '%') AND DocumentDate >= ISNULL(@StartDate, DATETIMEFROMPARTS(1753,1,1,0,0,0,0) AND DocumentDate <= ISNULL(@EndDate, DATETIMEFROMPARTS(9999,12,31,23,59,59,997)

Execução

Em tempo de execução, como o diagrama de sequência seguinte mostra, a definição da lista é apenas carregada quando o html é renderizado.

Posteriormente é feita a obtenção de dados para apresentação carregando apenas a definição da query em tempo de execução (Runtime Definition).

List View Properties Formatting

URL da lista

O query builder regista o seguinte estado angularJS:

/#/<tenant>/<organization>/<module>/<entity>/list?listname=<listname>

Em que cada uma das seguintes partes:

  • tenant: a chave to tenante
  • organization: chave da organização
  • module: chave do modulo que contem a lista
  • entity: entidade no plural da entidade principal da lista
  • listname: nome da lista, definido no modelo de apresentação

O angularJS invoca então o seguinte serviço do query builder que devolve o html para a list:

/#/<tenant>/<organization>/<module>/<entity>/list?queryKey=<listname>

OData

O serviço de dados de listas é inspirado em OData e está disponível no seguinte endereço:

/app/<tenant>/<organization>/<module>/<entity>/odata?listname=<listname>

O serviço tenta imitar o que seria de esperado por um serviço OData, com a limitação da infraestrutura de TVFs descrita.

Os comandos seguintes detalham o que é implementado do OData e o que teve de ser estendido ao protocolo de base para conseguir suportar todos os cenários necessários.

Este serviço aceita os seguintes parâmetros OData via query strings no url:

  • $skip: número do primeiro elemento da página
  • $top: número do último elemento da página
  • $orderby: parâmetro de ordenação

    O valor deste parâmetro contém as colunas e a respetiva direção de ordenação (“asc” ou “desc”). Por exemplo, para ordenar pela coluna DocumentDate ascendente e depois por Customer descendente:

…/odata?listname=simpleinvoices&$orderby=DocumentDate asc,Customer desc
  • $apply=groupby( ): O conteúdo de groupby contém as colunas a agrupar e a respetiva direção de ordenação (“asc” ou “desc”).

    Por exemplo, para agrupar pela data do documento:

…/odata?listname=simpleinvoices&$apply=groupby(DocumentDate asc)
  • $search: pesquisa textual. O valor deste parâmetro será pesquisado em todos os resultados com o critério “starts with”.
…/odata?listname=simpleinvoices&$search=A

Em extensão ao protocolo OData, O invocador pode alterar o critério de pesquisa com a utilização das funções seguintes: - startswith: pesquisa por entradas que comecem pelo texto especificado

…/odata?listname=simpleinvoices&$search=startswith(’A’)
  • contains: pesquisa por entradas que contenham o texto pesquisado
…/odata?listname=simpleinvoices&$search=contains(’A’)
  • endswith: pesquisa por entradas terminem no texto pesquisado
…/odata?listname=simpleinvoices&$search=endswith(’A’)
  • $filter=<FUNÇÃO>(<COLUNA>,’<PESQUISA>’)

    Este parâmetro permite a pesquisa apenas numa coluna especificada. Da mesma maneira que no serach, as funções suportas são:

  • startswith: pesquisa por entradas que comecem pelo texto especificado
…/odata?listname=simpleinvoices&$filter=startswith(PayableAmount,’1’)
  • contains: pesquisa por entradas que contenham o texto pesquisado
…/odata?listname=simpleinvoices&$filter=contains(PayableAmount,’1’)
  • endswith: pesquisa por entradas terminem no texto pesquisado
…/odata?listname=simpleinvoices&$filter=endswith(PayableAmount,’1’)

Obtenção da definição da lista

O serviço de listas obtém a definição da lista (QueryConfiguration), através de:

  • perspectiveKey : concatenação de “/”
  • queryKey: nome da lista

É feita a pesquisa na tabela QueryBuilder.QueryConfigurations, pelas chaves PerspectiveKey e QueryKey especificadas no pedido.

Drill down

A propriedade ViewActionType, na ListOperation do model de presentation configura de que maneira é possível fazer o drill-down. Os valores possíveis são:

  • Default: define que é possível fazer o drill down para uma view service operation
  • Custom: define que o implementador terá de implementar um serviço em que é devolvida a view service operation de drilldown.

View Action Type

Drill down – Custom

No caso de ser o implementador a implementar a ação será gerado no controller.app.cs do na classe do serviço correspondente a entidade o método “ViewCurrentRecord”:

/// <summary>
/// Gets the command action to view the Invoice instance with the specified id.
/// </summary>
/// <param name="id">The Invoice identifier.</param>
/// <returns>The HttpResponseMessage containing command action.</returns>
[Route("viewcurrentrecord")]
[HttpGet]
[ResponseType(typeof(CommandAction))]
public Task<CommandAction> ViewCurrentRecord(Guid id)
{           
        return this.OnViewCurrentRecord(id);
}

E na classe base do serviço é gerado o método “OnViewCurrentRecord”.

O implementador terá de implementar o override a este método e retornar a command action correspondente a view de drilldown.

No caso de não implementar, será devolvido um erro de maneira a forçar a implementação.

protected virtual async Task<CommandAction> OnViewCurrentRecord(Guid id)
{           
// TODO: copy the following commented section and implement the command action 
// to navigate to the view that edits the record with the specified id.

// CommandAction action = new CommandAction
// {
//    ModuleName = "sales",
//    ActionType = Primavera.Core.Services.CommandActionType.Navigate,
//    EntityName = "invoices",
//    OperationName = "editinvoice",
//    ShowMessage = false,
//    Parameters = new Dictionary<string, string> 
//    {
//        { "id", id.ToString() },
//    }
// };

// return action;

    throw new NotImplementedException();
}

Listas de lookup

As listas de lookup são utilizadas para ajudar na escolha de entidades relacionadas e são definidas da mesma maneira que as listas de utilizador no list model, mas não tem representação no modelo de serviços nem no modelo de apresentação.

As TVFs e a definição das queries (colunas) são criadas implicitamente apartir do modelo de entidades. Mais detalhes acerca deste assunto pode ser encontrado na Design Reference de Search.

Exportação para CSV

O utilizador tem a possibilidade de exportar o conteúdo da lista para um ficheiro csv. São dadas duas opções: exportar a página atual e exportar toda a lista.

Ambas as operações têm em conta a ordenação das colunas, os agrupamentos e possíveis filtros (a infraestrutura utilizada é a mesma desenhada para alimentar a lista). Quando a lista está filtrada (usando o campo de search no topo de página), a informação exportada é condizente com esse filtro. Isto é, a funcionalidade de exportação de lista exporta todas as páginas desta seleção.

Em termos de implementação, o método ToCsv (DataQueryController) utiliza a informação proveniente do DataQueryService sob a forma de uma DataTable. Dentro do método ToCsv, esta é tratada de forma a excluir colunas (colunas não visíveis, do tipo imagem, ou de sistema) e fazer o escape dos caracteres que podem influenciar a forma como os ficheiros .csv são lidos. O separador utilizado foi o ponto e vírgula, para que o Excel consiga automaticamente distrubuir os dados por colunas e linhas diferentes.

As opções estão disponíveis na view da lista na barra de ações secundárias. Os botões têm como ng-href uma variável que é mantida no controller listviewercontroller. Este é responsável por manter atualizadas as variáveis que fazem parte do URI (durante os vários eventos que vão ocorrendo na lista) e que transportam para o servidor as informações necessárias para extrair a informação (ordenação, paginação, número de registos por página, termo de pesquisa e agrupamentos).

Preservação de parâmetros em runtime

Os parâmetros das listas, quando definidos pelo utilizador, são preservados durante a sessão, permitindo assim que este navegue pela aplicação sem perder os valores definidos para cada parâmetro. Estes valores são preservados na session storage do browser através de uma setting chave-valor, cuja chave tem o seguinte formato: “usersetting::<módulo>_<serviço>_listname=<lista>”. No momento de consulta da lista, caso exista na session storage o setting correspondente a essa mesma lista, os valores default são substituídos pelos valores preservados.

Estes valores são preservados durante a duração da sessão (associada a uma tab do browser) e são apagados no momento de destruição da session storage, no clique no botão Reset dos parâmetros de uma lista e no logout.