Pesquisa
Este documento descreve o mecanismo de pesquisa e preenchimento de entidades relacionadas disponível para as aplicações Elevation 300.
Modelação
Entities Model
No entities model estão definidas as entidades e as suas relações.
Os lookup attributes definem a informação devolvida quando são feitas as pesquisas.

Os lookup atributos que são configurados como “Include on search”, são os atributos pesquisados. Por predefinição os atributos NaturalKey e Description são marcados como “Include on search”.

Services Model
Lookup Lists
No modelo de serviços podemos personalizar os serviços que suportam as listas de lookup e auto-complete específicos por associação:
- Definir colunas extra
- Esconder colunas
- Definir colunas calculadas com expressões SQL
- Definir filtros de pesquisa
Por exemplo, na entidade Invoice, a lista de lookup e o autocomplete para a entidade associada Invoice Type, campo DocumentType (Lookup Attribute) é aplicado filtro definido na propriedade Filter.
Parâmetros
É possível definir parâmetros para as listas de lookup e auto-complete. Os valores desses parâmetros são passados ao serviço de listas através do override do seguinte método do Service Manager da entidade:
AddLookupConditionsFor<Nome do Atributo>
Por exemplo, na entidade Invoice, a lista de lookup e auto-complete para a entidade associada Serie é aplicado, ao campo Serie, um filtro definido na propriedade Filter e é definido o parâmetro InvoiceType. O implementador terá que implementar o override de forma a passar o valor para esse parâmetro.
No exemplo seguinte, é obtido do draft da Invoice o valor da propriedade InvoiceTypeId selecionado para ser utilizado no filtro.
/// <summary>
/// Adds the lookup conditions for Series for the association attribute Serie.
/// </summary>
/// <param name="id">The entity identifier.</param>
/// <param name="options">The query options.</param>
protected override void AddLookupConditionsForSerie(Guid id, QueryOptions options)
{
IDraftService draftService = ObjectFactory.Get<IDraftService>();
if (draftService == null)
{
throw new ApplicationServiceMissingException(typeof(IDraftService));
}
string invoiceTypeId = draftService.GetProperty<string>(id, "documentTypeId");
if (options.Parameters == null)
{
options.Parameters = new Dictionary<string, object>();
}
options.Parameters.Add("InvoiceType", new Guid(invoiceTypeId));
base.AddLookupConditionsForSerie(id, options);
}
Colunas para filtragem
Ao definir os serviços de uma lista pode ser necessário fazer algum filtro sobre os valores que uma determinada coluna pode ter. Em alguns cenários é pretendido que essa coluna não esteja visível ao utilizador. Para isso existe a propriedade “Exclude From List”, que permite a utilização da coluna no filtro mas não é mostrada ao utilizador.
Por exemplo, na ListOperation InvoicePaymentMean, que se encontra na imagem anterior, está definida a coluna IsAvailableInSales para utilização em filtro.
Desta forma pode ser criado um filtro no qual só serão listados PaymentMeans que tenham a propriedade IsAvailableInSales igual a 1 (
IsAvailableInSales = 1).Service Filter
No modelo de serviços, é possível definir um filtro de por serviço para todas as listas de gestão de entidades que estejam associadas. Este filtro de serviço será também aplicado à pesquisa da entidade associada ao serviço.
O filtro de serviço permite:
- Definir as colunas de entidade necessárias para o filtro
- Definir colunas com expressão SQL, para utilizar no filtro
- Definir a expressão de filtragem
A lista de gestão de entidade pode ser configurada para não respeitar o filtro de serviço através da opção “Include Service Filter”. Por omissão, esta opção está ativada.
Implementação
Serviços
As listas de lookup são obtidas em duas fases:
- Obtenção da definição da lista
- Obtenção dos dados
Definição da lista
O serviço que devolve o HTML que contém a definição da lista está disponível no módulo QueryBuilder. As definições de lista são guardadas em base de dados na tabela do módulo do query builder QueryConfigurations e o mecanismo utilizado está detalhado na design reference referente a listas. Nesta tabela são persistidas todas as definições de lista, seja da entidade, ou as especificas de uma relação.
Definição da lista de entidade
O serviço que disponibiliza a lista de entidade tem o seguinte url:
app/{tenantKey}/{orgKey}/<modulo>/<entidade>/lookuplist
As listas de lookup são geradas com as seguintes chaves:
- perspectiveKey : “
/ ” - queryKey:
Lookup A definição da lista contem as colunas correspondentes ao lookup attributes definidos na entidade no modelo de entidades.
Por exemplo, para a entidade Sales.Items os lookupAttributes são:
- NaturalKey
- Description
A definição de lista de lookup principal seria a seguinte:
--- [Sales].[Items] Lookup Query
INSERT INTO [QueryBuilder].[QueryConfigurations] ([Id], [QueryKey], [Description], [PerspectiveKey], [EntityType], [DefaultQuery], [Definition], [NaturalKey], [RuntimeDefinition], [IsSystem])
VALUES (
'8234d1a5-a10b-471c-86b7-884efadfab87',
'ItemsLookup',
'RES_SYSQB_Sales_Item',
'Sales/Items',
'Primavera.Sales.Item',
1,
'<?xml version="1.0" encoding="utf-8" ?>
<Query
xmlns="http://www.primaverabss.com/reporting/querymodelxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="ItemsLookup"
Description="RES_MainLookupQuery_Item_Caption|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256"
User=""
ListServiceName="ItemsQuery"
SearchPlaceHolderResourceKey="RES_MainLookupQuery_Item_SearchPlaceHolder|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256"
HasSearch="True"
Principal="Sales/Item"
Perspective="Sales/Items">
<Query.Results>
<Column Name="NaturalKey" Visible="True" ResourceKey="RES_MainLookupQuery_Item_Column_NaturalKey|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" PresentationDataType="ShortText" CanSort="True" CanGroup="False">
<Column.Expression>
<AttributeExpression Attribute="NaturalKey"/>
</Column.Expression>
</Column>
<Column Name="Description" Visible="True" ResourceKey="RES_MainLookupQuery_Item_Column_Description|Primavera.Sales.Resources.ListsResources, Primavera.Sales.Domain.Entities, Version=1.0.0.0, PublicKeyToken=33086db60a481256" PresentationDataType="Text" CanSort="True" CanGroup="False">
<Column.Expression>
<AttributeExpression Attribute="Description"/>
</Column.Expression>
</Column>
</Query.Results>
</Query>
',
'8234d1a5-a10b-471c-86b7-884efadfab87',
null,
1
)
Definição da lista de entidades relacionadas
O serviço que disponibiliza a lista de entidade para pesquisar por entidades relacionadas tem o seguinte url:
app/{tenantKey}/{orgKey}/<moduloOrigem>/<entidadeOrigem>/lookuplist/<atributoDestino>?targetModule=<moduloDestino>&targetEntity<entidadeDestino>&targetPath=<caminho>
No caso de existirem listas de lookup específicas para uma relação, em que existem colunas extra ou em que alguma coluna foi escondida (as colunas pesquisáveis não podem ser escondidas), é então necessário gerar uma definição específica.
A querykey tem o seguinte formato:
<entidade/serviçoContexto>_<OrigemPlural>For<Caminho><AtributoDestino>Lookup
Por exemplo para a relação de SimpleInvoices para PaymentMean, há uma coluna que foi escondida, é então gerada a definição em que as chaves são:
- perspectiveKey: Patterns/PaymentMeans
- queryKey: SimpleInvoices_PaymentMeansForPaymentMeanLookup
Obtenção de dados
Os serviços de pesquisa estão disponíveis através das ações “search”, para efetuar pesquisa pela entidade em si, e “lookup”, para efetuar pesquisa por entidades relacionadas. A estes dois serviços podem ser invocados os seguintes verbos HTTP:
- GET: é invocado pelas directives angularjs para fazer o auto-complete. Utiliza o mesmo método do serviço POST com parametrizações específicas do auto-complete.
- POST: é invocado pelas lookup lists. Expõe todos os parâmetros necessários para pesquisar, paginar e ordenar.
Dados para a lista de entidades (ou Resources)
São gerados dois métodos nos controladores webapi, por entidade, para pesquisar a entidade ou resource em si que estão disponíveis na através da acção “search” (POST e GET):
app/{tenantKey}/{orgKey}/<modulo>/<entidade ou serviço>/search
Como se pode ver no código gerado abaixo para o resource items, por exemplo.
/// <summary>
/// Handles Item operations.
/// </summary>
[GeneratedCode("AthenaCodeGenerator", "3.0.0.0")]
[RoutePrefix("app/{tenantKey}/{orgKey}/sales/items")]
public partial class ItemsAppController : ItemsAppControllerBase
…
#region Lookup\Search Methods
#region Item
/// <summary>
/// Gets the lookup values for Item association.
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <returns>The HttpResponseMessage result.</returns>
[Route("search")]
[HttpGet]
[ResponseType(typeof(IQueryResult))]
public virtual Task<IQueryResult> GetItemsBySearchTerm(string searchTerm = null)
{
return this.GetItemsBySearchTerm(new QueryOptions { SearchTerm = searchTerm, PageSize = 10, CountResults = false });
}
/// <summary>
/// Gets the lookup values for Item association.
/// </summary>
/// <param name="options">The query options.</param>
/// <param name="skip">The skip.</param>
/// <param name="top">The top.</param>
/// <returns>The HttpResponseMessage result.</returns>
[Route("search")]
[HttpPost]
[ResponseType(typeof(IQueryResult))]
public virtual async Task<IQueryResult> GetItemsBySearchTerm(
QueryOptions options,
[FromUri(Name = "$skip")] string skip = null,
[FromUri(Name = "$top")] string top = null)
{
using (ItemsServiceManager mng = new ItemsServiceManager())
{
var searchResult = await mng.GetItemsBySearchTerm(options);
return searchResult;
}
}
O controlador chama por sua vez os métodos do ServiceManager da entidade.
Dados para preencher a lista de entidades relacionadas
São gerados dois serviços para pesquisar a entidades relacionadas. Estes estão disponíveis na rota “lookup” (POST e GET).
app/{tenantKey}/{orgKey}/<moduloOrigem>/<entidadeOrigem>/lookup/<atributoDestino>?targetModule=<moduloDestino>&targetEntity<entidadeDestino>&targetPath=<caminho>
Por exemplo, como se pode ver no código gerado abaixo na entidade items para pesquisar a entidade relacionada baseunit.
#region [Item] BaseUnit
/// <summary>
/// Gets the lookup values for [Item] BaseUnit association.
/// </summary>
/// <param name="id">The draft identifier.</param>
/// <param name="searchTerm">The search term.</param>
/// <returns>The HttpResponseMessage result.</returns>
[Route("{id}/lookup/baseUnit")]
[HttpGet]
[ResponseType(typeof(IQueryResult))]
public virtual Task<IQueryResult> GetUnitsForBaseUnitBySearchTerm(Guid id, string searchTerm = null)
{
return this.GetUnitsForBaseUnitBySearchTerm(id, new QueryOptions { SearchTerm = searchTerm, PageSize = 10, CountResults = false});
}
/// <summary>
/// Gets the lookup values for [Item] BaseUnit association.
/// </summary>
/// <param name="id">The draft identifier.</param>
/// <param name="queryOptions">The query options.</param>
/// <returns>The HttpResponseMessage result.</returns>
[Route("{id}/lookup/baseUnit")]
[HttpPost]
[ResponseType(typeof(IQueryResult))]
public virtual async Task<IQueryResult> GetUnitsForBaseUnitBySearchTerm(Guid id, [FromBody] QueryOptions queryOptions, [FromUri(Name = "$skip")] string skip = null, [FromUri(Name = "$top")] string top = null)
{
int skipValue;
if (!string.IsNullOrEmpty(skip) && int.TryParse(skip, out skipValue))
{
queryOptions.RowIndex = skipValue;
}
int topValue;
if (!string.IsNullOrEmpty(top) && int.TryParse(top, out topValue))
{
queryOptions.PageSize = topValue;
}
if (queryOptions.SearchTerm != null)
{
queryOptions.SearchTerm = System.Web.HttpContext.Current.Server.UrlDecode(queryOptions.SearchTerm);
}
using (ItemsServiceManager mng = new ItemsServiceManager())
{
var lookupResult = await mng.GetUnitsForBaseUnitBySearchTerm(id, queryOptions);
// result
return lookupResult;
}
}
Fonte de Dados - TVFs
As listas de lookup e do auto-complete utilizam a mesma fonte de dados: as TVFs que são geradas. O ficheiro
Pesquisa da Entidade ou da Entidade no Serviço
A pesquisa de entidade ou da entidade via serviço utiliza as TVFs com o nome:
[<modulo>].[<entidade ou serviço>Query]
As TVFs que suportam a pesquisa expõem dois parâmetros:
- SearchTerm: termo de pesquisa
- User: utilizador para validação de acesso (ainda não está a ser utilizado)
Como resultado são devolvidas as colunas marcadas na entidade como lookup attributes e o campo Id.
Por exemplo, para a entidade Sales.Items os lookupAttributes são:
- NaturalKey
- Description
A definição da TVF de pesquisa é a seguinte:
CREATE Function [Sales].[ItemsQuery]
(
@searchTerm nvarchar(100),
@user nvarchar(100)
)
RETURNS TABLE
AS
RETURN
(
SELECT
[Id],
[NaturalKey],
[Description]
FROM (
SELECT
[T1].[Id],
[T1].[NaturalKey],
[T1].[Description] as [Description]
FROM
[Sales].[Items] [T1]
WHERE
[T1].[IsDeleted] = 0 AND
[T1].[NaturalKey] COLLATE Latin1_general_CI_AI LIKE @searchTerm COLLATE Latin1_general_CI_AI OR [T1].[Description] COLLATE Latin1_general_CI_AI LIKE @searchTerm COLLATE Latin1_general_CI_AI)
) SRC
)
Service Filter
Os serviços que têm service filters associados terão as pesquisas pela entidade via serviço com o filtro do service filter. Na TVF gerada para pesquisa é utilizada uma view SQL filtrada, invés da view SQL da entidade.
Esta view sql filtrada é igual a view da entidade mas os resultados são filtrados pela condição especificada no filtro.
Por exemplo, o serviço de SimpleInvoices, tem um ServiceFilter, então é gerada uma TVF para suportar a pesquisa por SimpleInvoices, em que os dados são obtidos da view SQL filtrada InvoicesForSimpleInvoicesServiceView.
CREATE Function [Sales].[InvoicesForSimpleInvoicesServiceQuery]
(
@searchTerm nvarchar(100),
@user nvarchar(100)
)
RETURNS TABLE
AS
RETURN
(
SELECT
[Id],
[NaturalKey],
[BuyerCustomerPartyName]
FROM (
SELECT
[T1].[Id],
[T1].[NaturalKey],
[T1].[BuyerCustomerPartyName] as [BuyerCustomerPartyName]
FROM
[Sales].[InvoicesForSimpleInvoicesServiceView] [T1]
WHERE
([T1].[NaturalKey] COLLATE Latin1_general_CI_AI LIKE @searchTerm COLLATE Latin1_general_CI_AI OR [T1].[BuyerCustomerPartyName] COLLATE Latin1_general_CI_AI LIKE @searchTerm COLLATE Latin1_general_CI_AI)
) SRC
)
Pesquisa de Entidades Relacionadas (chaves estrangeiras)
A pesquisa de entidade relacionada utiliza as TVFs com o nome:
[<modulo>].[<entidadeContexto>_<entidadeOrigem><caminhoAtéCampoDestino><campoDestino>Query]
Como resultado são devolvidas as colunas marcadas na entidade de origem como lookup attributes e o campo Id.
As entidades relacionadas podem ser um de dois tipos:
- Master: Não existe caminho até ao campo destino. Por exemplo: ´´´[Sales].[Invoice_CurrenciesForCurrencyQuery]´´´
- Detail: Existe caminho até ao campo destino. Por exemplo: ´´´[Sales].[Invoice_ItemsForDocumentLinesItemQuery]´´´
Master
Por exemplo, para a entidade destino Invoices, campo Currency tem a entidade origem Patterns.Currencies em que os lookupAttributes são:
- NaturalKey
- Description
- CurrencyUnit
- Symbol
A definição da TVF, de suporte a pesquisa de currencies para o campo currency da entidade invoice é a seguinte:
CREATE Function [Sales].[Invoice_CurrenciesForCurrencyQuery]
(
@searchTerm nvarchar(100),
@user nvarchar(100)
)
RETURNS TABLE
AS
RETURN
(
SELECT
[Q].[Id], [Q].[NaturalKey], [Q].[Description], [Q].[CurrencyUnit], [Q].[Symbol]
FROM [Patterns].[CurrenciesQuery](@searchTerm, @user) AS [Q]
JOIN [Patterns].[Currencies] AS [T] ON [T].[Id] = [Q].[Id]
WHERE [T].[IsActive] = 1
)
Detail
Por exemplo, a entidade destino InvoicesLines, campo Item tem a entidade origem Items em que os lookupAttributes são:
- NaturalKey
- Description
A definição da TVF, de suporte a pesquisa de Items para o campo Item na entidade detalhe documentLines, que é um detalhe da entidade de contexto Invoice é a seguinte:
CREATE Function [Sales].[Invoice_ItemsForDocumentLinesItemQuery]
(
@searchTerm nvarchar(100),
@user nvarchar(100)
)
RETURNS TABLE
AS
RETURN
(
SELECT
[Q].[Id], [Q].[NaturalKey], [Q].[Description]
FROM [Sales].[ItemsQuery](@searchTerm, @user) AS [Q]
JOIN [Sales].[Items] AS [T] ON [T].[Id] = [Q].[Id]
WHERE [T].[IsActive] = 1
)
Extensões
As entidades que são extensões são também suportadas funcionalidades de auto-complete e lookup lists.
As extensões tem exatamente o mesmo comportamento que entidades não extensões.
TVFs
Internamente as extensões são obtidas com a sua base e os lookup attributes da base e da extensão. É possível ver no exemplo abaixo na TVF gerada para a relação de Invoice para CustomerParty que é uma extensão de Party:
- a verde é feito o join com a base através do campo BaseEntityId.
- a amarelo é feita a projeção dos lookup attributes da base e da extensão

Atributos Extra
A TVF que é gerada para pesquisar colunas de extensões com atributos extras, tem de ter em conta os atributos extras que são da entidade estendida ou da entidade extensão.
A tabela da entidade que inclui o atributo de procura (de lookup), no caso das extensões, tem de ser um join da entidade estendida com a entidade extensão para que todos os atributos necessários para pesquisa sejam projetados (a verde no exemplo abaixo).
Estes atributos podem ser para apresentar no resultado, como para obter atributos de entidades relacionadas. Por exemplo, abaixo a verde é feita a seleção de AssortmentId, que apesar de não ser uma coluna apresentada é utilizada para obter a ParentAssortment que tem uma coluna que é para apresentar.
Pesquisa com Atributos Extra
Além dos lookup attributes definidos na entidade de origem, é possível definir lookup atributes extra para pesquisar entidades numa relação específica.
É possível utilizar estes atributos extra para filtragem no campo de filtro.
Ao existirem ListOperations deste tipo, além da TVF especifica gerada, será também gerada uma definição de lista específica para esta relação que irá conter as colunas extras.
O nome da query key da definição específica para a relação tem o seguinte formato:
<EntidadeContextoPlural>_<EntidadePesquisaPlural>For<CaminhoAtéAtributo><NomeDoAtributo>Lookup
Por exemplo: Invoices_CustomerPartiesForBuyerCustomerPartyLookup.
Por exemplo, como está de seguida é possível uma ListOperation, do tipo lookup para a entidade Invoice, para pesquisa de customers para o campo BuyerCustomerParty, definir atributos extras, através das opções List Operations -> Select Attributes.

Componentes
O componente de pesquisa tem com objectivo explorar e selecionar dados de uma lista.
A pesquisa é composta por dois componentes: FormlySearch e PriFieldSearch. O componente PriFieldSearch é responsável por toda a lógica e interface da pesquisa, enquanto o FormlySearch é um wrapper para integrar a pesquisa no gestor de formlulários Formly.

FormlySearch
O FormlySearch é responsável por interligar o Formly com o PriFieldSearch. Este componente intermédio é necessário uma vez que é necessário um tratamento prévio dos dados que são fornecidos pelo Formly.
PriFieldSearch
O PriFieldSearch é o componente responsável pela parte gráfica da pesquisa assim como pela exebição do resultado selecionado e pela seleção de uma nova pesquisa.
Visualmente, este componente tem o estado de edição e de visualização. No estado de edição, o input de pesquisa fica disponível, sendo possível efetuar uma pesquisa pelos items em questão e a sua seleção. No estado de visualização, o componente encontra-se em modo de leitura, não podendo ser feito nenhum tipo de pesquisa.
FormSearchControl (FormControl)
O FormSearchControl é uma implementação especifica do FormControl. É necessário expandir o FormControl com novas funcionalidades por forma a permitir a comunicação entre o DynamicFormControl e o PriFieldSearch. Este control contém uma série de eventos com o objectivo do DynamicFormControl subscrever por forma a efetuar as ações de pesquisa e as ações de contexto.
Fluxo da pesquisa
A pesquisa é baseada em eventos. Quando é efetuada uma pesquisa no PriFieldSeach é despelotado um evento que será propagado até ao DraftModelService. Este serviço executará a pesquisa via pedido REST para o servidor e retornanda a resposta numa sequência de eventos até ao PriFieldSearch para o mesmo exibir os resultados ao utilizador. O fluxo da pesquisa está no seguinte diagrama:

Ações de contexto
As opções de contexto permitem expandir as opções do compoente de pesquisa de forma simples e transparente.

Existem quatro opções de contexto:
- Lista: Abre a lista de seleção da entidade respectiva;
- Criar: Navega para a página de criação de nova entidade;
- Registo: Navega para o registo, caso já exista um registo selecionado;
- Detalhe: Exibe o dashboard de contexto para a entidade selecionada.
Imagem no campo de pesquisa (Entity Thumbnail Image)
A funcionalidade Entity Thumbnail Image permite que em campos de pesquisa, uma imagem seja apresentada junto do campo e nos resultados da pesquisa.
![]()
Para que a imagem apareça, basta:
- Aceder modelo de entidades;
- Procurar a entidade que está associada à pesquisa;
- Aceder às propriedades de um campo do tipo Image;
- Colocar a propriedade Entity Thumbnail Image com o valor verdadeiro;

- Fazer synchronize all system lookup attributes (entity model → Entites → Syncrhonize All System Lookup Attributes);
- Fazer synchronize all columns information no modelo de serviços (alt + enter → List Operations → Synchronize All Columns Information);

Para o caso de a entidade é ser utilizada noutros modulos, no modelo com a dependecia:
- Garatir que a interface tem a propriedade colocada como Entity Thumbnail Image da entidade base;

- Garantir que a External Entity tem a propriedade que foi selecionada como Entity Thumbnail Image. Caso apareça na caixa Select attributes e não na External Entity, basta clicar no botão ok da caixa Select attributes;

- Fazer o passo 5 e 6 referidos anteriormente.
Notas
Não esquecer que para o caso dos detalhes de um picking, o resource é gerado com a propriedade que deverá conter o valor da image. Este deve ser preenchido e enviado na resposta ao pedido da informação das linhas do detalhe no código custom.




