Gestion des paramètres dans les applications métier similaire à un système de contrôle de version

image

Dans diverses applications, la tâche se pose régulièrement de soutenir la logique du changement dans le temps d'un attribut d'un objet par rapport à un certain sujet (ou sujets). Par exemple, cela peut être un changement dans le prix de détail des marchandises en magasin ou des indicateurs KPI pour les employés.

Dans cet article, je montrerai quelle logique de domaine et quelles interfaces peuvent être construites pour résoudre ce problème. Je ferai immédiatement une réserve pour que cela concerne l'influence managériale de l'utilisateur sur l'attribut, et non le reflet du changement historique.

L'implémentation sera présentée sur la base de la plate-forme lsFusion ouverte et gratuite, mais un schéma similaire peut être appliqué lors de l'utilisation de toute autre technologie.

Présentation


Pour une présentation et une compréhension plus simples de l'article, nous prenons le prix comme attribut, le produit comme objet, et l'entrepôt sera le sujet. Dans ce cas, l'intervalle minimum possible pour définir l'attribut sera la date. Ainsi, l'utilisateur pourra déterminer quel sera le prix pour une date particulière pour tout produit et entrepôt.

Le schéma d'entrée utilisateur pour les changements de prix sera similaire à celui utilisé dans les systèmes de contrôle de version classiques. Tout changement, du point de vue de la logique du domaine, sera une validation unique, sur la base de laquelle le statut pour une certaine date sera calculé. Dans de nombreux domaines, ces commits sont appelés documents ou transactions. Dans ce cas, par ce commit, nous entendons la soi-disant liste de prix. Chaque liste de prix précisera les marchandises et les entrepôts qui y sont inclus, ainsi que la période de validité.

Le schéma décrit présente les avantages suivants:

  • Atomicité . Chaque modification est publiée dans un document distinct. Par conséquent, ces documents peuvent être temporairement enregistrés, mais pas publiés. Avec une entrée erronée, il est facile d'annuler la modification entière.
  • La transparence Il est facile de déterminer qui a fait le changement et quand, ainsi que d'en indiquer la raison en faisant un commentaire sur le document.

La principale différence avec le système de contrôle de version est que les validations explicites sont indépendantes les unes des autres. Ainsi, il est possible de supprimer toutes les validations à tout moment sans douleur. De plus, chaque validation peut être définie pour se terminer lorsqu'elle cesse de fonctionner, ce qui n'est bien sûr pas dans le système de contrôle de version.

Implémentation


Nous commençons la définition de la logique du domaine avec les entrepôts. Compliquons un peu la solution en combinant les entrepôts dans une hiérarchie d'un groupe de profondeur dynamique. Par quel principe cela est fait est décrit dans l' article correspondant, donc je vais juste donner un morceau de code qui déclare les groupes et crée des formulaires pour les éditer:

Annonce du groupe d'entrepôt
CLASS Group ' ' ;
name '' = DATA ISTRING [ 50 ] (Group);

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 ;

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

EDIT Group OBJECT g
;

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

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

Exemple de hiérarchie de groupe
image


Ensuite, déclarez les entrepôts qui peuvent être liés à l'un des groupes:

Annonce d'entrepôt
CLASS Stock '' ;
name '' = DATA ISTRING [ 50 ] (Stock);

group '' = DATA Group (Stock);
nameGroup '' (Stock st) = name(group(st));

FORM stock ''
OBJECTS s = Stock PANEL
PROPERTIES (s) name, nameGroup

EDIT Stock OBJECT s
;

FORM stocks ''
OBJECTS s = Stock
PROPERTIES (s) READONLY name, nameGroup
PROPERTIES (s) NEWSESSION NEW , EDIT , DELETE

LIST Stock OBJECT s
;

NAVIGATOR {
NEW stocks;
}


Exemple d'entrepôt
image


Et enfin, déclarez la logique de la marchandise:

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

FORM product ''
OBJECTS p = Product PANEL
PROPERTIES (p) name

EDIT Product OBJECT p
;

FORM products ''
OBJECTS p = Product
PROPERTIES (p) READONLY name
PROPERTIES (p) NEWSESSION NEW , EDIT , DELETE

LIST Product OBJECT p
;

NAVIGATOR {
NEW products;
}

Exemple de produit
image


Nous procédons directement à la création de la logique des tarifs. Tout d'abord, nous définissons la classe de liste de prix elle-même, ainsi que sa période de validité:
CLASS PriceList '-' ;
fromDate ' ' = DATA DATE (PriceList);
toDate ' ' = DATA DATE (PriceList);
Nous pensons que si aucune date n'a été fixée, la liste de prix est infinie.
Nous ajoutons un événement qui, lors de la création de la liste de prix, inscrira automatiquement la date actuelle à partir de laquelle il commencera à fonctionner.
WHEN LOCAL SET (PriceList p IS PriceList) DO
fromDate(p) <- currentDate();
Le mot clé LOCAL signifie que l'événement ne se déclenchera pas lorsque la sauvegarde est appliquée à la base de données, mais immédiatement lorsque la modification est effectuée.

Ajoutez ensuite l'utilisateur qui l'a créé et l'heure de création:
createdTime ' ' = DATA DATETIME (PriceList);
createdUser = DATA User (PriceList);
nameCreatedUser '' (PriceList p) = name(createdUser(p));
Créez maintenant un événement qui les remplira automatiquement:
WHEN SET (PriceList p IS PriceList) DO {
createdTime(p) <- currentDateTime();
createdUser(p) <- currentUser();
}
Cet événement, contrairement au précédent, ne sera déclenché que lorsque vous cliquerez sur le bouton Enregistrer. C'est-à-dire lors d'une transaction de sauvegarde dans la base de données.

Ensuite, créez les lignes de liste de prix dans lesquelles les marchandises et les prix seront définis:
CLASS PriceListDetail ' -' ;
priceList = DATA PriceList (PriceListDetail) NONULL DELETE ;

product = DATA Product (PriceListDetail);
nameProduct '' (PriceListDetail d) = name(product(d));

price '' = DATA NUMERIC [ 10 , 2 ] (PriceListDetail);
L'attribut NONULL indique que la propriété priceList doit toujours être définie et DELETE indique que lorsque la valeur de la propriété est mise à zéro (par exemple, lors de la suppression de la liste de prix), la ligne correspondante doit être automatiquement supprimée.

Pour une utilisation future, nous créerons des propriétés qui détermineront la période de validité des lignes de liste de prix:
fromDate ' ' (PriceListDetail d) = fromDate(priceList(d));
toDate ' ' (PriceListDetail d) = toDate(priceList(d));
Nous allons maintenant lier la liste de prix aux entrepôts pour lesquels elle opèrera. Tout d'abord, ajoutez la propriété principale, ce qui sera vrai si le groupe entier d'entrepôts est inclus dans la liste de prix:
dataIn '' = DATA BOOLEAN (PriceList, Group);
Nous calculons «l'inclusion» du groupe en tenant compte des parents sélectionnés (comme décrit dans l'article sur les hiérarchies):
in ' ()' (PriceList p, Group child) =
GROUP LAST dataIn(p, Group parent) ORDER DESC level(child, parent) WHERE dataIn(p, parent);
Ajoutez la propriété principale, avec laquelle vous pouvez spécifier que la liste de prix agit sur un entrepôt spécifique:
dataIn '' = DATA BOOLEAN (PriceList, Stock);
Nous calculons la propriété finale, qui déterminera que la liste de prix change les prix dans l'entrepôt correspondant, en tenant compte des groupes:
in '' (PriceList p, Stock s) = dataIn(p, s) OR in(p, group(s));
Créez une propriété qui affichera les noms de tous les groupes et entrepôts sélectionnés de la liste de prix, pour un utilisateur plus pratique de visualiser la liste des listes de prix:
stocks '' (PriceList p) = CONCAT ' / ' ,
GROUP CONCAT name(Group g) IF dataIn(p, g), ',' ORDER g,
GROUP CONCAT name(Stock s) IF dataIn(p, s), ',' ORDER s
CHARWIDTH 30 ;
La dernière étape de la description de la logique du domaine calculera directement le prix actuel des marchandises dans l'entrepôt. Pour ce faire, créez une propriété qui trouve la dernière ligne de date de la liste de prix avec les marchandises, l'entrepôt et la période de validité souhaités:
priceListDetail (Product p, Stock s, DATE dt) =
GROUP LAST PriceListDetail d
ORDER fromDate(d), d
WHERE product(d) = p AND in(priceList(d), s) AND
fromDate(d) <= dt AND NOT toDate(d) < dt;
Dans la logique de calcul de cette propriété, différentes variations sont possibles. Vous pouvez modifier à la fois le filtre pour les lignes de frappe (par exemple, en ajoutant une condition dans OERE la liste de prix est publiée) et la commande. Il est à noter que l'objet lui-même, ou plutôt son identifiant interne, a été ajouté à l'ordre de sélection par le deuxième paramètre. Cela est nécessaire pour que la valeur du prix soit toujours déterminée d'une manière unique.

Sur la base de la liste de prix reçue, nous déterminons la valeur du prix et sa période de validité:
price '' (Product p, Stock s, DATE dt) = price(priceListDetail(p, s, dt));
fromDate ' ' (Product p, Stock s, DATE dt) = fromDate(priceListDetail(p, s, dt));
toDate ' ' (Product p, Stock s, DATE dt) = toDate(priceListDetail(p, s, dt));
Ils seront ensuite utilisés dans les tables d'interface utilisateur.

Ensuite, nous passons à la construction de l'interface utilisateur. Tout d'abord, nous dessinons un formulaire pour modifier la liste de prix. Créez un formulaire et ajoutez-y l’en-tête du document:
FORM priceList '-'
OBJECTS p = PriceList PANEL
PROPERTIES (p) fromDate, toDate

EDIT PriceList OBJECT p
;
Ajoutez la ligne de liste de prix au formulaire:
EXTEND FORM priceList
OBJECTS d = PriceListDetail
PROPERTIES (d) nameProduct, price
PROPERTIES (d) NEW , DELETE
FILTERS priceList(d) = p
;
Ensuite, ajoutez un arbre dans lequel il y aura à la fois des groupes et des entrepôts:
EXTEND FORM priceList
TREE stocks g = Group PARENT parent, s = Stock
PROPERTIES READONLY name(g), name(s)
PROPERTIES dataIn(p, g), in(p, g)
PROPERTIES dataIn(p, s), in(p, s)
FILTERS group(s) = g
;
Les propriétés des groupes et des entrepôts sont ajoutées simultanément à l'arborescence. La plate-forme affichera, selon l'objet, telle ou telle propriété dans l'ordre dans lequel elles sont ajoutées au formulaire.

Nous personnalisons la conception du formulaire afin que les marchandises et les entrepôts soient dessinés dans des onglets séparés:
DESIGN priceList {
OBJECTS {
NEW pane {
fill = 1 ;
type = TABBED ;
MOVE BOX (d) { caption = '' ; }
MOVE BOX ( TREE stocks) { caption = '' ; }
}
}
}
Le formulaire d'édition ressemblera à ceci:

image

image

Reste à construire la forme de base de la gestion des prix. Il comprendra deux onglets. Le premier affichera une liste de toutes les listes de prix (similaire à la liste des commits). Le deuxième onglet affichera les prix actuels d'un entrepôt spécifique pour la date sélectionnée.

Pour implémenter le premier onglet, ajoutez au formulaire une liste de listes de prix avec des lignes pour un aperçu rapide:
FORM managePrices ' '
OBJECTS p = PriceList
PROPERTIES (p) READONLY fromDate, toDate, stocks, createdTime, nameCreatedUser
PROPERTIES (p) NEWSESSION NEW , EDIT , DELETE

OBJECTS d = PriceListDetail
PROPERTIES (d) READONLY nameProduct, price
FILTERS priceList(d) = p
;
Pour le deuxième onglet, nous ajoutons d'abord la date à laquelle les prix sont affichés, l'arborescence des groupes de magasins, ainsi que les magasins eux-mêmes:
EXTEND FORM managePrices
OBJECTS dt = DATE PANEL
PROPERTIES VALUE (dt)

TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)

OBJECTS s = Stock
PROPERTIES (s) READONLY name, nameGroup
FILTERS level(group(s), g)
;
La liste des entrepôts affichera tous les entrepôts qui sont les descendants du groupe sélectionné en haut.

Ensuite, ajoutez au formulaire une liste de marchandises pour lesquelles il existe des prix valides pour l'entrepôt à la date sélectionnée:
EXTEND FORM managePrices
OBJECTS pr = Product
PROPERTIES READONLY name(pr), price(pr, s, dt), fromDate(pr, s, dt), toDate(pr, s, dt)
FILTERS price(pr, s, dt)
;
Le prix lui-même et la période de validité sont ajoutés aux colonnes. Vous pouvez également ajouter le numéro de liste de prix - alors ce tableau ressemblera à la logique des annotations dans les systèmes de contrôle de version.

Afin que l'utilisateur comprenne d'où vient un tel prix, nous ajoutons la liste des lignes de liste de prix avec les marchandises et les entrepôts appropriés:
EXTEND FORM managePrices
OBJECTS prd = PriceListDetail
PROPERTIES READONLY BACKGROUND (priceListDetail(pr, s, dt) = prd)
fromDate(prd), toDate(prd), '' = stocks(priceList(prd)), price(prd)
FILTERS product(prd) = pr AND in(priceList(prd), s)
;
À l'aide de l'attribut BACKGROUND, mettez en surbrillance la ligne qui a déterminé le prix indiqué dans le tableau.

De plus, pour la commodité de l'utilisateur, nous ajouterons la possibilité d'ouvrir le formulaire d'édition de la liste de prix correspondante dans une nouvelle session immédiatement à partir de cette histoire:
edit (PriceListDetail d) + { edit(priceList(d)); }
EXTEND FORM managePrices
PROPERTIES (prd) NEWSESSION EDIT
;
Pour ce faire, vous devez spécifier l'action qui sera effectuée lorsque vous essayez de modifier une ligne en implémentant l'action de modification intégrée. Ensuite, un bouton standard pour modifier un objet via un appel de dialogue est ajouté au formulaire de la manière standard.

Et enfin, nous formons le design final du formulaire:
DESIGN managePrices {
OBJECTS {
NEW pane {
fill = 1 ;
type = TABBED ;
NEW priceLists {
caption = '-' ;
MOVE BOX (p);
MOVE BOX (d);
}
NEW prices {
caption = '' ;
fill = 1 ;
type = SPLITH ;
NEW leftPane {
MOVE BOX (dt);
MOVE BOX ( TREE groups);
MOVE BOX (s);
}
NEW rightPane {
fill = 3 ;
type = SPLITV ;
MOVE BOX (pr) { fill = 3 ; }
MOVE BOX (prd);
}
}
}
}
}
Ici, le conteneur de volet est ajouté en premier, qui se compose de deux onglets: listes de prix et prix . Le premier d'entre eux ajoute simplement une liste de listes de prix et de lignes. Dans le second, deux panneaux sont créés: leftPane et rightPane . Le panneau de gauche contient la date et les entrepôts, et le panneau de droite contient l'historique des marchandises et des prix.

Résultat


Considérez les principales options d'utilisation de la logique résultante.

Supposons que nous ayons deux listes de prix distinctes pour différents groupes de marchandises. Ensuite, en fonction de l'entrepôt sélectionné, dans l'onglet des prix, seuls les produits des listes de prix correspondantes seront affichés:

image

Créez maintenant une nouvelle liste de prix avec une période de validité limitée, une liste de magasins dépouillée et un nouveau prix. Dans le deuxième onglet, si nous sélectionnons une date dans la plage de la nouvelle liste de prix, nous en obtiendrons un nouveau. Dès l'expiration de la période de validité, l'ancien prix reviendra à nouveau du prix d'origine:

image

En utilisant le même mécanisme, vous pouvez «annuler» l'action de prix spécifiques à partir d'une certaine date. Par exemple, si vous entrez un nouveau prix sans spécifier de prix, il se trouve que le prix sera réinitialisé et les marchandises disparaîtront du filtre. Dans ce cas, lors de la suppression du document saisi, tout revient à l'ancien état:

image

La propriété résultante avec le prix des marchandises par l'entrepôt à la date peut être en outre utilisée dans divers événements ou d'autres formes. Par exemple, vous pouvez effectuer une tarification automatique dans une commande basée sur cette logique de tarification:
WHEN LOCAL CHANGED (sku(UserOrderDetail d)) OR CHANGED (stock(d)) OR CHANGED (dateTime(d)) DO
price(d) <- price(sku(d), stock(d), dateTime(d));
Un bon avantage dans cette logique est que lorsque vous ajoutez un nouvel entrepôt au groupe, les prix des listes de prix déjà créées lui seront automatiquement appliqués. La même chose se produit lorsque vous modifiez le groupe de l'entrepôt.

Si vous le souhaitez, vous pouvez rendre la colonne avec le prix sur l'onglet avec les prix actuels modifiable et ajouter un bouton qui créera un nouvel engagement pour les prix modifiés.

Conclusion


Dans la solution au niveau de la plateforme, ni les ouvrages de référence, ni les documents avec des chaînes, ni les registres, ni les rapports et autres abstractions inutiles ne sont utilisés. Tout se fait exclusivement sur les concepts de classes et de propriétés. Notez que cette logique assez complexe a été implémentée dans environ 150 lignes de code significatives sur lsFusion. Le mettre en œuvre dans la même formulation sur d'autres plateformes (par exemple, 1C) est une tâche beaucoup plus difficile.

Le schéma décrit ci-dessus est largement utilisé dans la solution ERP basée sur lsFusion. Son utilisation, avec diverses modifications, prend en charge les listes de prix des fournisseurs, les prix de détail de gestion, les stocks et de nombreux autres paramètres de gestion.

Le modèle peut être compliqué en ajoutant plusieurs entités au document (par exemple, un fournisseur peut être ajouté à l'entrepôt), ainsi qu'en définissant plusieurs attributs dans un document à la fois. En particulier, vous pouvez ajouter le type de prix de l'entité et, dans la ligne de document, définir le prix du tuple de la ligne et le type de prix correspondant. Dans la logique décrite ci-dessus, il vous suffit d'ajouter quelques paramètres supplémentaires à certaines propriétés.

À l'aide de plusieurs lignes de code supplémentaires, il est possible de dénormaliser tous les enregistrements de modification dans une seule table sur laquelle construire l'index correspondant. Ensuite, la sélection de n'importe quelle valeur pour n'importe quelle date sera effectuée dans un temps logarithmique. Une telle optimisation est nécessaire lorsqu'il y a plusieurs centaines de millions d'enregistrements dans ce tableau.

Vous pouvez essayer l'exemple construit en ligne sur la page correspondante du site (section Plateforme). Voici tout le code source que vous devez coller dans le champ souhaité:

Code source
REQUIRE Authentication, Time;

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

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 ;

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

EDIT Group OBJECT g
;

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

LIST Group OBJECT g
;

NAVIGATOR {
NEW groups;
}

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

group '' = DATA Group (Stock);
nameGroup '' (Stock st) = name(group(st));

FORM stock ''
OBJECTS s = Stock PANEL
PROPERTIES (s) name, nameGroup

EDIT Stock OBJECT s
;

FORM stocks ''
OBJECTS s = Stock
PROPERTIES (s) READONLY name, nameGroup
PROPERTIES (s) NEWSESSION NEW , EDIT , DELETE

LIST Stock OBJECT s
;

NAVIGATOR {
NEW stocks;
}

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

FORM product ''
OBJECTS p = Product PANEL
PROPERTIES (p) name

EDIT Product OBJECT p
;

FORM products ''
OBJECTS p = Product
PROPERTIES (p) READONLY name
PROPERTIES (p) NEWSESSION NEW , EDIT , DELETE

LIST Product OBJECT p
;

NAVIGATOR {
NEW products;
}

CLASS PriceList '-' ;
fromDate ' ' = DATA DATE (PriceList);
toDate ' ' = DATA DATE (PriceList);

createdTime ' ' = DATA DATETIME (PriceList);
createdUser = DATA User (PriceList);
nameCreatedUser '' (PriceList p) = name(createdUser(p));

WHEN LOCAL SET (PriceList p IS PriceList) DO
fromDate(p) <- currentDate();

WHEN SET (PriceList p IS PriceList) DO {
createdTime(p) <- currentDateTime();
createdUser(p) <- currentUser();
}

CLASS PriceListDetail ' -' ;
priceList = DATA PriceList (PriceListDetail) NONULL DELETE ;

product = DATA Product (PriceListDetail);
nameProduct '' (PriceListDetail d) = name(product(d));

price '' = DATA NUMERIC [ 10 , 2 ] (PriceListDetail);

fromDate ' ' (PriceListDetail d) = fromDate(priceList(d));
toDate ' ' (PriceListDetail d) = toDate(priceList(d));

dataIn '' = DATA BOOLEAN (PriceList, Group);

in ' ()' (PriceList p, Group child) =
GROUP LAST dataIn(p, Group parent) ORDER DESC level(child, parent) WHERE dataIn(p, parent);

dataIn '' = DATA BOOLEAN (PriceList, Stock);
in '' (PriceList p, Stock s) = dataIn(p, s) OR in(p, group(s));

stocks '' (PriceList p) = CONCAT ' / ' ,
GROUP CONCAT name(Group g) IF dataIn(p, g), ',' ORDER g,
GROUP CONCAT name(Stock s) IF dataIn(p, s), ',' ORDER s
CHARWIDTH 30 ;

priceListDetail (Product p, Stock s, DATE dt) =
GROUP LAST PriceListDetail d
ORDER fromDate(d), d
WHERE product(d) = p AND in(priceList(d), s) AND
fromDate(d) <= dt AND NOT toDate(d) < dt;

price '' (Product p, Stock s, DATE dt) = price(priceListDetail(p, s, dt));
fromDate ' ' (Product p, Stock s, DATE dt) = fromDate(priceListDetail(p, s, dt));
toDate ' ' (Product p, Stock s, DATE dt) = toDate(priceListDetail(p, s, dt));

FORM priceList '-'
OBJECTS p = PriceList PANEL
PROPERTIES (p) fromDate, toDate

EDIT PriceList OBJECT p
;

EXTEND FORM priceList
OBJECTS d = PriceListDetail
PROPERTIES (d) nameProduct, price
PROPERTIES (d) NEW , DELETE
FILTERS priceList(d) = p
;

EXTEND FORM priceList
TREE stocks g = Group PARENT parent, s = Stock
PROPERTIES READONLY name(g), name(s)
PROPERTIES dataIn(p, g), in(p, g)
PROPERTIES dataIn(p, s), in(p, s)
FILTERS group(s) = g
;

DESIGN priceList {
OBJECTS {
NEW pane {
fill = 1 ;
type = TABBED ;
MOVE BOX (d) { caption = '' ; }
MOVE BOX ( TREE stocks) { caption = '' ; }
}
}
}

FORM managePrices ' '
OBJECTS p = PriceList
PROPERTIES (p) READONLY fromDate, toDate, stocks, createdTime, nameCreatedUser
PROPERTIES (p) NEWSESSION NEW , EDIT , DELETE

OBJECTS d = PriceListDetail
PROPERTIES (d) READONLY nameProduct, price
FILTERS priceList(d) = p
;

EXTEND FORM managePrices
OBJECTS dt = DATE PANEL
PROPERTIES VALUE (dt)

TREE groups g = Group PARENT parent
PROPERTIES READONLY name(g)

OBJECTS s = Stock
PROPERTIES (s) READONLY name, nameGroup
FILTERS level(group(s), g)
;

EXTEND FORM managePrices
OBJECTS pr = Product
PROPERTIES READONLY name(pr), price(pr, s, dt), fromDate(pr, s, dt), toDate(pr, s, dt)
FILTERS price(pr, s, dt)
;

EXTEND FORM managePrices
OBJECTS prd = PriceListDetail
PROPERTIES READONLY BACKGROUND (priceListDetail(pr, s, dt) = prd)
fromDate(prd), toDate(prd), '' = stocks(priceList(prd)), price(prd)
FILTERS product(prd) = pr AND in(priceList(prd), s)
;

edit (PriceListDetail d) + { edit(priceList(d)); }
EXTEND FORM managePrices
PROPERTIES (prd) NEWSESSION EDIT
;

DESIGN managePrices {
OBJECTS {
NEW pane {
fill = 1 ;
type = TABBED ;
NEW priceLists {
caption = '-' ;
MOVE BOX (p);
MOVE BOX (d);
}
NEW prices {
caption = '' ;
fill = 1 ;
type = SPLITH ;
NEW leftPane {
MOVE BOX (dt);
MOVE BOX ( TREE groups);
MOVE BOX (s);
}
NEW rightPane {
fill = 3 ;
type = SPLITV ;
MOVE BOX (pr) { fill = 3 ; }
MOVE BOX (prd);
}
}
}
}
}

NAVIGATOR {
NEW managePrices;
}

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


All Articles