Trabalhando com hierarquias no lsFusion

imagem

Em várias aplicações, geralmente é necessário implementar uma representação hierárquica de objetos. Normalmente, isso é usado para classificá-los especificando grupos. Esses grupos formam uma árvore de profundidade dinâmica, que é usada para navegação, agregação de dados e configuração de parâmetros.

Neste artigo, mostrarei como essa lógica pode ser implementada na plataforma lsFusion aberta e gratuita.

Como exemplo, vamos usar uma lógica simples na qual é necessário implementar a lógica de mercadorias agrupadas em grupos específicos que formam uma hierarquia de profundidade dinâmica. Nesse caso, as mercadorias podem ser vinculadas a um nó intermediário da árvore.

Primeiro, de acordo com o esquema padrão, declare a entidade do grupo de produtos como uma classe simples e plana, com formulários de edição e uma lista:
CLASS Group '' ;
name '' = DATA ISTRING [ 50 ] (Group);

FORM group ''
OBJECTS g = Group PANEL
PROPERTIES (g) name

EDIT Group OBJECT g
;

FORM groups ''
OBJECTS g = Group
PROPERTIES (g) READONLY name
PROPERTIES (g) NEWSESSION NEW , EDIT , DELETE

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

Agora vamos criar uma hierarquia de grupos. Para fazer isso, apresentamos uma propriedade que conterá um link para o grupo pai:
parent = DATA Group (Group);
nameParent ' ' (Group g) = name(parent(g));

Em seguida, criamos uma propriedade que determinará recursivamente o relacionamento entre dois grupos:
level '' (Group child, Group parent) =
RECURSION 1l IF child IS Group AND parent = child
STEP 2l IF parent = parent($parent) MATERIALIZED ;

Em que princípio o operador RECURSION funciona , não descreverei neste artigo, mas a propriedade level retornará 2 ao grau de "comprimento do caminho entre filho e pai na árvore direcional correspondente". MATERIALIZED indica que a plataforma deve armazená-la em uma tabela separada, onde para cada par de nós conectados haverá um registro separado com o valor do nível na coluna correspondente. Com qualquer alteração na estrutura da árvore, esta tabela será recontada automaticamente.

Por exemplo, para uma árvore assim:

imagem

A tabela ficará assim:

imagem

Nele, key0 é o código descendente e key1 é o código pai. O número de entradas nesta tabela será aproximadamente igual ao número de grupos vezes a profundidade média da árvore. Esse esquema de armazenamento será útil, pois se você precisar contar todos os descendentes do grupo, não precisará recorrer a solicitações de CTE, mas poderá usar o JOIN usual para esta tabela.

Além disso, com base na propriedade construída, o nome canônico do grupo pode ser calculado:
canonicalName ' ' (Group group) =
GROUP CONCAT name(Group parent), ' / ' ORDER DESC level(group, parent) CHARWIDTH 50 ;

Por exemplo, para o grupo Milk na figura acima, o nome canônico seria All / Groceries / Dairy / Milk . CHARWIDTH é especificado para informar à plataforma qual largura usar para esta propriedade (em caracteres) ao criar a interface.

Agora vamos expandir o formulário para visualizar e editar grupos com propriedades recém-criadas:
EXTEND FORM group
PROPERTIES (g) nameParent, canonicalName
;

EXTEND FORM groups
PROPERTIES (g) READONLY nameParent, canonicalName
;

Um formulário com uma lista de grupos em um formulário simples terá a seguinte aparência:

imagem

Depois que a lógica dos grupos for concluída, adicione a entidade Product :
CLASS Product '' ;
name '' = DATA ISTRING [ 50 ] (Product);

Crie um link de produto para o grupo de produtos ao qual ele pertence:
group '' = DATA Group (Product);
canonicalNameGroup ' ' (Product p) = canonicalName(group(p));

Por fim, criaremos um formulário para entrada de mercadorias, no qual haverá dois elementos: uma árvore de grupos e uma lista de mercadorias. Para o grupo de árvores selecionado, apenas os produtos que pertencem a qualquer descendente do nó selecionado serão exibidos na lista. Primeiro, declare um formulário e adicione uma árvore a ele com uma lista de grupos:
FORM products ''
TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)
;

Usando o comando TREE , uma árvore é criada a partir de objetos da classe Group , cuja hierarquia é determinada pela propriedade pai criada anteriormente.

Adicione o formulário ao navegador:
NAVIGATOR {
NEW products;
}

Neste exemplo, a entrada e a edição de mercadorias serão realizadas não por meio de diálogos separados, mas diretamente no próprio formulário. Para fazer isso, crie uma ação para criar um produto com referência ao grupo selecionado:
newProduct '' (Group g) {
NEW p = Product {
group(p) <- g;
}
}

Agora, no formulário criado anteriormente, adicione a lista de produtos com colunas editáveis:
EXTEND FORM products
OBJECTS p = Product
PROPERTIES (p) name, canonicalNameGroup
FILTERS level(group(p), g)
;

Use os botões do formulário para adicionar e remover mercadorias:
EXTEND FORM products
PROPERTIES newProduct(g) DRAW p TOOLBAR , DELETE (p)
;

Como a ação newProduct é definida para um grupo de produtos, é necessário especificar explicitamente que ela deve ser adicionada à barra de ferramentas com a lista de produtos (p).

Resta configurar o design para que a árvore seja exibida à esquerda e a lista de produtos à direita e entre eles haja um separador, com o qual você pode redimensionar objetos:
DESIGN products {
OBJECTS {
NEW pane {
type = SPLITH ;
fill = 1 ;
MOVE BOX ( TREE groups);
MOVE BOX (p);
}
}
}

O formulário final ficará assim:

imagem

Após a criação da hierarquia de produtos e grupos, geralmente é necessário definir algum parâmetro em qualquer um dos níveis. Além disso, quanto menor a hierarquia for especificada, maior será o seu valor. Por exemplo, se o grupo Laticínios estiver definido como 30 e o grupo Leite estiver definido como 20, o último deverá ser selecionado.

Suponha que você queira definir a opção premium dessa maneira. Para fazer isso, primeiro crie a propriedade apropriada para o grupo:
markup ', %' = DATA NUMERIC [ 10 , 2 ] (Group);

Para encontrar o valor desejado, basta usar o agrupamento com a escolha do último valor:
parentMarkup ' ( ), %' (Group child) =
GROUP LAST markup(Group parent) ORDER DESC level(child, parent) WHERE markup(parent);

Traduzida para o idioma comum, essa expressão encontra ( GROUP ) a última marcação ( LAST ) no grupo superior ( pai do grupo ), em ordem decrescente de distância a ele ( nível de ORDER DESC (filho, pai) ), para o qual essa marcação fornecido ( marcação WHERE (pai) ). Aqui, quero observar como lsFusion corresponde à linguagem natural.

Adicione as propriedades criadas acima ao formulário do produto na árvore de grupos:
EXTEND FORM products
PROPERTIES (g) markup, parentMarkup READONLY
;

Suponha que exista uma necessidade de definir um prêmio diretamente para um produto e que ele seja maior que o prêmio do grupo. Para fazer isso, primeiro crie a propriedade principal do produto:
dataMarkup ' , %' = DATA NUMERIC [ 10 , 2 ] (Product);

Em seguida, declaramos uma propriedade que retornará o prêmio do produto, se um for especificado, ou o prêmio do grupo:
markup ', %' (Product p) = OVERRIDE dataMarkup(p), parentMarkup(group(p));

Depois disso, adicione as duas propriedades ao formulário:
EXTEND FORM products
PROPERTIES (p) dataMarkup, markup READONLY
;

O mecanismo para definir prêmios para grupos e produtos terá a seguinte aparência:

imagem

Conclusão


No artigo acima, fomos capazes de criar a lógica dos produtos, combiná-los em grupos com uma hierarquia de profundidade dinâmica e também fornecer ao usuário a capacidade de definir prêmios em qualquer nível. Tudo isso levou cerca de 70 linhas de código significativas. Você pode tentar como funciona on-line, bem como fazer alterações no código na seção apropriada do site (guia Plataforma). Aqui está todo o código-fonte que você precisa colar no campo apropriado:

Código fonte
CLASS Group '' ;
name '' = DATA ISTRING [ 50 ] (Group);

FORM group ''
OBJECTS g = Group PANEL
PROPERTIES (g) name

EDIT Group OBJECT g
;

FORM groups ''
OBJECTS g = Group
PROPERTIES (g) READONLY name
PROPERTIES (g) NEWSESSION NEW , EDIT , DELETE

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

parent = DATA Group (Group);
nameParent ' ' (Group g) = name(parent(g));

level '' (Group child, Group parent) =
RECURSION 1l IF child IS Group AND parent = child
STEP 2l IF parent = parent($parent) MATERIALIZED ;

canonicalName ' ' (Group group) =
GROUP CONCAT name(Group parent), ' / ' ORDER DESC level(group, parent) CHARWIDTH 50 ;

EXTEND FORM group
PROPERTIES (g) nameParent, canonicalName
;

EXTEND FORM groups
PROPERTIES (g) READONLY nameParent, canonicalName
;

CLASS Product '' ;
name '' = DATA ISTRING [ 50 ] (Product);

group '' = DATA Group (Product);
canonicalNameGroup ' ' (Product p) = canonicalName(group(p));

FORM products ''
TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)
;

NAVIGATOR {
NEW products;
}

newProduct '' (Group g) {
NEW p = Product {
group(p) <- g;
}
}
EXTEND FORM products
OBJECTS p = Product
PROPERTIES (p) name, canonicalNameGroup
FILTERS level(group(p), g)
;

EXTEND FORM products
PROPERTIES newProduct(g) DRAW p TOOLBAR , DELETE (p)
;

DESIGN products {
OBJECTS {
NEW pane {
type = SPLITH ;
fill = 1 ;
MOVE BOX ( TREE groups);
MOVE BOX (p);
}
}
}

markup ', %' = DATA NUMERIC [ 10 , 2 ] (Group);

parentMarkup ' ( ), %' (Group child) =
GROUP LAST markup(Group parent) ORDER DESC level(child, parent) WHERE markup(parent);

EXTEND FORM products
PROPERTIES (g) markup, parentMarkup READONLY
;

dataMarkup ' , %' = DATA NUMERIC [ 10 , 2 ] (Product);
markup ', %' (Product p) = OVERRIDE dataMarkup(p), parentMarkup(group(p));

EXTEND FORM products
PROPERTIES (p) dataMarkup, markup READONLY
;

O modelo descrito acima pode ser modificado e usado de várias maneiras, adicionando parâmetros adicionais às propriedades. Por exemplo, em uma implementação do sistema ERP, os prêmios para grupos e mercadorias são definidos dessa maneira não globalmente, mas separadamente para cada tipo de preço. Além disso, a implementação em complexidade não é diferente do exemplo descrito acima.

Source: https://habr.com/ru/post/pt468047/


All Articles