Trabajando con jerarquías en lsFusion

imagen

En diversas aplicaciones, a menudo es necesario implementar una representación jerárquica de objetos. Normalmente, esto se usa para clasificarlos especificando grupos. Estos grupos forman un árbol de profundidad dinámica, que luego se utiliza para la navegación, la agregación de datos y la configuración de parámetros.

En este artículo, mostraré cómo se puede implementar esta lógica en la plataforma lsFusion abierta y gratuita.

Como ejemplo, tomemos una lógica simple en la que necesita implementar la lógica de los bienes agrupados en grupos específicos que forman una jerarquía de profundidad dinámica. En este caso, los bienes se pueden vincular a un nodo intermedio del árbol.

Primero, de acuerdo con el esquema estándar, declare la entidad del grupo Producto como una clase plana simple con formularios de edición y una 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;
}

Ahora hagamos una jerarquía de grupos. Para hacer esto, presentamos una propiedad que contendrá un enlace al grupo principal:
parent = DATA Group (Group);
nameParent ' ' (Group g) = name(parent(g));

A continuación, creamos una propiedad que determinará recursivamente la relación entre dos grupos:
level '' (Group child, Group parent) =
RECURSION 1l IF child IS Group AND parent = child
STEP 2l IF parent = parent($parent) MATERIALIZED ;

Sobre qué principio funciona el operador RECURSION , no lo describiré en este artículo, pero la propiedad de nivel devolverá 2 al grado de "longitud de ruta entre el elemento secundario y el elemento primario en el árbol direccional correspondiente". MATERIALIZADO indica que la plataforma debe almacenarlo en una tabla separada, donde para cada par de nodos conectados habrá un registro separado con el valor de nivel en la columna correspondiente. Con cualquier cambio en la estructura del árbol, esta tabla se relatará automáticamente.

Por ejemplo, para tal árbol:

imagen

La tabla se verá así:

imagen

En él, key0 es el código descendiente y key1 es el código padre. El número de entradas en esta tabla será aproximadamente igual al número de grupos multiplicado por la profundidad promedio del árbol. Tal esquema de almacenamiento será útil ya que si necesita contar a todos los descendientes del grupo, no tiene que recurrir a las solicitudes de CTE, pero puede usar la UNIÓN habitual para esta tabla.

Además, en función de la propiedad construida, se puede calcular el nombre canónico del grupo:
canonicalName ' ' (Group group) =
GROUP CONCAT name(Group parent), ' / ' ORDER DESC level(group, parent) CHARWIDTH 50 ;

Por ejemplo, para el grupo Leche en la imagen de arriba, el nombre canónico sería Todo / Comestibles / Lácteos / Leche . CHARWIDTH se especifica para indicarle a la plataforma qué ancho usar para esta propiedad (en caracteres) al construir la interfaz.

Ahora expandiremos el formulario para ver y editar grupos con propiedades recién creadas:
EXTEND FORM group
PROPERTIES (g) nameParent, canonicalName
;

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

Un formulario con una lista de grupos en forma plana se verá así:

imagen

Una vez completada la lógica de los grupos, agregue la entidad Producto :
CLASS Product '' ;
name '' = DATA ISTRING [ 50 ] (Product);

Cree un enlace de producto al grupo de productos al que pertenece:
group '' = DATA Group (Product);
canonicalNameGroup ' ' (Product p) = canonicalName(group(p));

Finalmente, crearemos un formulario para ingresar bienes, en el que habrá dos elementos: un árbol de grupos y una lista de bienes. Para el grupo de árboles seleccionado, solo se mostrarán en la lista los productos que pertenezcan a cualquier descendiente del nodo seleccionado. Primero, declare un formulario y agregue un árbol con una lista de grupos:
FORM products ''
TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)
;

Usando el comando TREE , se crea un árbol a partir de objetos de la clase Group , cuya jerarquía está determinada por la propiedad padre creada anteriormente.

Agregue el formulario al navegador:
NAVIGATOR {
NEW products;
}

En este ejemplo, la entrada y edición de bienes se llevará a cabo no a través de diálogos separados, sino directamente en el formulario mismo. Para hacer esto, cree una acción para crear un producto con referencia al grupo seleccionado:
newProduct '' (Group g) {
NEW p = Product {
group(p) <- g;
}
}

Ahora, en el formulario creado anteriormente, agregue la lista de productos con columnas editables:
EXTEND FORM products
OBJECTS p = Product
PROPERTIES (p) name, canonicalNameGroup
FILTERS level(group(p), g)
;

Agregue los botones de formulario para agregar y eliminar productos:
EXTEND FORM products
PROPERTIES newProduct(g) DRAW p TOOLBAR , DELETE (p)
;

Dado que la acción newProduct se define para un grupo de productos, debe indicar explícitamente que debe agregarse a la barra de herramientas con la lista de productos (p).

Queda por configurar el diseño para que el árbol se muestre a la izquierda, y la lista de productos esté a la derecha, y entre ellos hay un separador, con el que puede cambiar el tamaño de los objetos:
DESIGN products {
OBJECTS {
NEW pane {
type = SPLITH ;
fill = 1 ;
MOVE BOX ( TREE groups);
MOVE BOX (p);
}
}
}

La forma final se verá así:

imagen

Después de crear la jerarquía de productos y grupos, a menudo es necesario establecer algún parámetro en cualquiera de los niveles. Además, cuanto menor es la jerarquía especificada, mayor es su valor. Por ejemplo, si el grupo de productos lácteos se establece en 30 y el grupo de productos lácteos se establece en 20, entonces se debe seleccionar el último.

Suponga que desea definir la opción premium de esta manera. Para hacer esto, primero cree la propiedad apropiada para el grupo:
markup ', %' = DATA NUMERIC [ 10 , 2 ] (Group);

Para encontrar el valor deseado, simplemente use la agrupación con la elección del último valor:
parentMarkup ' ( ), %' (Group child) =
GROUP LAST markup(Group parent) ORDER DESC level(child, parent) WHERE markup(parent);

Traducido al lenguaje ordinario, esta expresión encuentra ( GRUPO ) el último marcado ( ÚLTIMO ) en el grupo superior ( Grupo principal ), en orden decreciente de distancia ( nivel ORDEN DESC (secundario, principal) ), para el cual este marcado dado ( DONDE marcado (padre) ). Aquí quiero señalar cómo lsFusion corresponde al lenguaje natural.

Agregue las propiedades creadas anteriormente al formulario del producto en el árbol de grupo:
EXTEND FORM products
PROPERTIES (g) markup, parentMarkup READONLY
;

Suponga que existe la necesidad de establecer una prima directamente para un producto, y que debería ser más alta que la prima para el grupo. Para hacer esto, primero cree la propiedad primaria para el producto:
dataMarkup ' , %' = DATA NUMERIC [ 10 , 2 ] (Product);

Luego declaramos una propiedad que devolverá la prima del producto, si se especifica una, o la prima del grupo:
markup ', %' (Product p) = OVERRIDE dataMarkup(p), parentMarkup(group(p));

Después de eso, agregue ambas propiedades al formulario:
EXTEND FORM products
PROPERTIES (p) dataMarkup, markup READONLY
;

El mecanismo para establecer primas para grupos y productos se verá así:

imagen

Conclusión


En el artículo anterior, pudimos crear la lógica de los productos, combinarlos en grupos con una jerarquía de profundidad dinámica y también proporcionar al usuario la capacidad de establecer primas en cualquiera de los niveles. Todo esto tomó alrededor de 70 líneas de código significativas. Puede probar cómo funciona en línea, así como realizar cambios en el código en la sección correspondiente del sitio (pestaña Plataforma). Aquí está todo el código fuente que necesita pegar en el campo apropiado:

Código fuente
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
;

La plantilla descrita anteriormente se puede modificar y usar de varias maneras agregando parámetros adicionales a las propiedades. Por ejemplo, en una implementación del sistema ERP, las primas para grupos y bienes se establecen de esta manera no globalmente, sino por separado para cada tipo de precio. Además, la implementación en complejidad no es diferente del ejemplo descrito anteriormente.

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


All Articles