Dans de nombreuses organisations, l'évaluation des unités est effectuée à l'aide de KPI (Key Performance Indicators). Dans l'organisation où je travaille, un tel système est appelé «système d'indicateurs de performance», et dans cet article, je veux parler de la façon dont nous avons réussi à automatiser une partie du travail avec des indicateurs en un mois. Avec tout cela, nos coûts de main-d'œuvre n'étaient pas les plus élevés, mais en même temps, nous avons essayé de mettre en œuvre une liste de souhaits de longue date. Dans mon histoire, il n'y aura pas de technologies ou de révélations hype (après tout, le développement provincial est dur), mais il y aura quelques croquis sur le sujet qui aideront à comprendre comment nous avons commencé, ce que nous avons fait et quelles pensées nous avons tirées du développement. Si vous ne vous êtes pas encore ennuyé, je demande un chat.
Contexte
En 2006, nous avons introduit un système d'indicateurs de performance: quinze critères d'évaluation ont été élaborés, une méthodologie pour les calculer et une fréquence trimestrielle de calcul de ces indicateurs a été établie. L'évaluation a été réalisée pour vingt-deux branches de l'organisation situées dans toutes les régions de notre région. Et pour que les indicateurs soient atteints avec beaucoup d'enthousiasme, une prime leur était attachée - plus la somme des indicateurs est élevée, plus la place dans la notation est élevée, plus la place dans la notation est élevée, plus la prime est élevée, et ainsi de suite chaque trimestre et chaque année.
Au fil du temps, la composition des critères a changé: chaque trimestre, de nouveaux ont été ajoutés ou d'anciens ont été exclus. Au plus fort, vers 2016, le nombre d'indicateurs dépassait la quarantaine, et maintenant il n'y en a plus que quatorze.
Cependant, pendant tout ce temps, le processus de calcul a été le même. Chaque critère est calculé par l'unité responsable de l'organisation mère selon un algorithme approuvé spécifiquement pour ce critère. Le calcul peut être effectué à la fois par une formule simple et par un certain nombre de formules complexes, il peut nécessiter la collecte de données de plusieurs systèmes, alors qu'il est très probable que l'algorithme changera assez souvent. Et puis tout est déjà beaucoup plus simple: l'indicateur calculé en pourcentage est multiplié par son coefficient, ainsi, un score est obtenu par le critère, puis les points sont classés et chaque unité se déroule en fonction du score. La même chose se fait avec la somme des points pour calculer la place finale.
Dès le début, Excel a été utilisé pour prendre en compte et calculer tout ce qui est décrit ci-dessus. Pendant de nombreuses années, l'ensemble final de critères et le calcul ultérieur des points et des lieux ont été effectués dans une belle assiette, dont une partie est illustrée sur la figure.

Je note seulement que sur cette jolie tablette, la plupart des enceintes sont simplement cachées, mais en réalité au 4ème trimestre 2016 ça ressemblait à ça

D'accord, un tel nombre d'indicateurs et un tel volume du tableau n'ajoutent pas à sa simplicité. De plus, chacun des critères a été calculé séparément, et les filles du département général ont réalisé ce tableau récapitulatif de leurs mains. Étant donné que non seulement la composition des critères a changé, mais aussi que les calculs ont pu être refaits plusieurs fois (pour de nombreuses raisons), les derniers jours de chaque trimestre, pour le moins, n'étaient pas satisfaits. J'ai dû constamment changer les données du tableau, le vérifier et le revérifier: comme ça, vous oubliez d'ajouter une nouvelle colonne à la somme des points ou vous ne mettez pas à jour le coefficient, et pour certaines personnes l'endroit diminue, et avec lui le bonus est moins. Par conséquent, après la réduction héroïque de la table, une vérification non moins héroïque a commencé, tous ceux qui ont considéré chaque critère, les gestionnaires et même un informaticien assigné à l'organisation ont vérifié et accepté. Étant donné que tout le travail a été effectué manuellement et que le calcul des indicateurs pour le tableau croisé dynamique a été envoyé par la poste, il y a souvent eu des situations où le tableau croisé dynamique contenait des erreurs dans les formules et les données des versions non pertinentes. Le capitaine des preuves indique qu'après chaque erreur a été détectée, le processus de vérification a été redémarré.
Lorsque tous les indicateurs sont calculés, la tablette est approuvée et envoyée aux succursales, là, dans les succursales, elle a été revérifiée: et si Excel insidieux pensait que quelque chose n'allait pas? Je pense qu'il est évident que la compilation d'un rapport annuel sur les critères ou l'analyse des données historiques s'est avérée être une quête non moins passionnante.
Cependant, d'un point de vue managérial, un tel système est assez efficace et permet, si nécessaire, de resserrer les zones d'affaissement du travail, de suivre et d'avoir une idée de ce qui se passe dans chacune des branches dans un domaine d'activité particulier. De plus, l'emplacement de la succursale, calculé selon les critères, était un indicateur intégral.
Le système doit changer
La pratique a prouvé la nécessité et l'importance d'un système d'évaluation. Début 2017, il est devenu clair que le calcul des critères une fois par trimestre permettait d'évaluer le travail effectué, mais le suivi lui permettait d'être faible. Durée trop longue. À cet égard, il a été décidé que le calcul des indicateurs serait effectué une fois toutes les deux semaines, et les résultats seraient résumés tout de même tous les trimestres. L'augmentation de la fréquence de calcul a vraiment permis aux directeurs d'agence de réagir rapidement aux changements et de contrôler davantage les processus au sein de leurs unités.
Mais seulement dans l'organisation mère, peu étaient satisfaits de la perspective de mener à bien le processus de collecte de données décrit ci-dessus, pas tous les trimestres, mais toutes les deux semaines. Comme ce n'est pas difficile à deviner, ils ont décidé d'automatiser le processus. Les délais se sont avérés serrés: du moment de la décision de passer à une nouvelle fréquence de calcul, à la transition proprement dite, seul un mois aurait dû s'écouler. Pour cette période, il était très souhaitable de trouver quelque chose de mieux qu'un tas d'Excel pour le code et le calcul, et un courrier pour la collecte et la notification.
Au début, il y a eu pas mal de discussions animées sur ce qui devait vraiment, vraiment être fait automatiquement et le calcul des indicateurs lui-même, en tenant compte de toutes leurs formules et sources de données. Mais étant donné les délais serrés, la complexité d'une telle fonctionnalité et la nécessité de sa maintenance constante dans l'état actuel ont réussi à répondre aux exigences suivantes pour le système:
- Un manuel de référence des critères doit être conservé, préservant l'historique de leur évolution;
- Il devrait être possible de saisir et de stocker les indicateurs calculés, ainsi que leur conversion en points comme dans la même plaque;
- Sur la base des valeurs des indicateurs, un rapport devrait être généré et accessible à toutes les parties intéressées;
- Naturellement, tout cela devrait être fourni avec une interface Web.
La fonctionnalité est très petite, mais pas beaucoup de temps.
Début du développement
J'ai toujours été attiré par les moyens de développement rapide et de génération automatique d'interfaces. Dans les cas où il est nécessaire de mettre en œuvre la fonctionnalité CRUD, l'idée quand l'interface et une partie de la logique métier seront fournies prêtes à l'emploi, pour ainsi dire, semble très tentante. Bien sûr, l'interface sera une logique sans prétention et maladroite, mais pour de nombreuses tâches, cela suffit.
Poussé par ces idées, j'ai décidé d'essayer quelque chose comme ça. Il existe Spring Roo, Cuba et d'autres outils intéressants, mais le choix s'est porté sur OpenXava. Premièrement, une fois que j'ai déjà fait une application très simple sur celui-ci et que j'étais satisfait, et deuxièmement, ce cadre à l'époque s'inscrivait avec succès dans notre pile technologique. De plus, il est très agréable d'avoir un petit tutoriel en russe.
Une brève description des fonctionnalités et capacités d'OpenXava peut être trouvée
ici . OpenXava est un framework qui implémente la construction automatique d'une interface web intégrée à une base de données basée sur JPA, et utilise des annotations pour décrire les règles de visualisation. L'application est basée sur des composants métier - des classes Java qui contiennent les informations nécessaires pour créer des applications. Ces informations comprennent la structure des données, les validateurs, les représentations valides, le mappage aux tables de base de données. Les opérations sur les composants métier sont effectuées via des contrôleurs qui peuvent CRUD à partir d'une boîte, rechercher, exporter au format PDF, etc. L'application OpenXava présente un ensemble de modules. Un module associe un composant métier à un ou plusieurs contrôleurs. Les représentations définies pour chaque composant métier sont utilisées pour afficher l'interface. Rien d'inhabituel, MVC avec un peu de sa propre atmosphère.
Stockage de données
Dans la plupart des applications, nous utilisons le SGBD IBM DB2. Une petite base de données a été créée qui stocke les livres de référence des groupes de critères et des critères selon lesquels les branches sont évaluées, le répertoire des branches et une plaque dans laquelle les valeurs calculées des critères sont entrées. Pour chaque critère, à un certain moment, un coefficient est attribué qui sera utilisé dans la notation. Chaque branche, pour chaque critère, reçoit également une évaluation pour une certaine date. Les données sur les valeurs des critères sont stockées historiquement, c'est-à-dire que les données pertinentes pour n'importe quelle date seront celles qui ont été entrées à la date la plus proche dans le passé. Cette approche, inspirée des registres d'informations de 1C: Enterprise, me semble assez pratique: il y a un historique et les problèmes d'édition / suppression ne sont pas particulièrement importants.
Structure de la base de donnéesCREATE TABLE SUMMAR.CLS_DEPART ( ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, PARENT_ID BIGINT NOT NULL DEFAULT 0, IS_DELETED INT DEFAULT 0, NAME CLOB, CODE VARCHAR(255), PRIMARY KEY (ID) ); CREATE TABLE SUMMAR.CLS_CRITERIA ( ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, IS_DELETED INT DEFAULT 0, NAME CLOB, CODE VARCHAR(255), PRIMARY KEY (ID) ); CREATE TABLE SUMMAR.CLS_GROUP_CRITERIA ( ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, IS_DELETED INT DEFAULT 0, NAME CLOB, CODE VARCHAR(255), PRIMARY KEY (ID) ); CREATE TABLE SUMMAR.REG_STATE_CRITERIA ( ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, ID_CRITERIA BIGINT NOT NULL, ID_GROUP_CRITERIA BIGINT NOT NULL, TIME_BEGIN TIMESTAMP NOT NULL DEFAULT CURRENT TIMESTAMP, TIME_END TIMESTAMP NOT NULL DEFAULT '9999-12-31-23.59.59.000000000000', TIME_CREATE TIMESTAMP NOT NULL DEFAULT CURRENT TIMESTAMP, KOEFFICIENT DECIMAL(15, 2), PRIMARY KEY (ID), CONSTRAINT FK_CRITERIA FOREIGN KEY (ID_CRITERIA) REFERENCES SUMMAR.CLS_CRITERIA(ID) ON DELETE NO ACTION ON UPDATE RESTRICT, CONSTRAINT FK_GROUP_CRITERIA FOREIGN KEY (ID_GROUP_CRITERIA) REFERENCES SUMMAR.CLS_GROUP_CRITERIA(ID) ON DELETE NO ACTION ON UPDATE RESTRICT ); CREATE TABLE SUMMAR.REG_VALUE_CRITERIA ( ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, ID_CRITERIA BIGINT NOT NULL, ID_GROUP_CRITERIA BIGINT NOT NULL, ID_DEPART BIGINT NOT NULL, DATE_REG TIMESTAMP(12) NOT NULL DEFAULT CURRENT TIMESTAMP, TIME_END TIMESTAMP NOT NULL DEFAULT '9999-12-31-23.59.59.000000000000', TIME_BEGIN TIMESTAMP NOT NULL DEFAULT CURRENT TIMESTAMP, PERCENT DECIMAL(15, 5), VAL DECIMAL(15, 5), PRIMARY KEY (ID), CONSTRAINT FK_CRITERIA FOREIGN KEY (ID_CRITERIA) REFERENCES SUMMAR.CLS_CRITERIA(ID) ON DELETE NO ACTION ON UPDATE RESTRICT, CONSTRAINT FK_DEPART FOREIGN KEY (ID_DEPART) REFERENCES SUMMAR.CLS_DEPART(ID) ON DELETE NO ACTION ON UPDATE RESTRICT, CONSTRAINT FK_GROUP_CRITERIA FOREIGN KEY (ID_GROUP_CRITERIA) REFERENCES SUMMAR.CLS_GROUP_CRITERIA(ID) ON DELETE NO ACTION ON UPDATE RESTRICT );
Afin d'obtenir le rapport convoité sur les performances des branches de la base de données, des fonctions stockées ont été créées, dont les résultats sont déjà mappés à la classe Java. Les fonctions ressemblent à ceci.
Nous obtenons d'abord tous les critères valides pour la date, ainsi que les coefficients des critères pertinents pour cette date CREATE OR REPLACE FUNCTION SUMMAR.SLICE_STATE_ALL_CRITERIA ( PMAX_TIME TIMESTAMP ) RETURNS TABLE ( ID_CRITERIA BIGINT, ID_GROUP_CRITERIA BIGINT, TIME_BEGIN TIMESTAMP, TIME_END TIMESTAMP, TIME_CREATE TIMESTAMP, KOEFFICIENT DECIMAL(15, 2) ) LANGUAGE SQL RETURN SELECT RSC.ID_CRITERIA, RSC.ID_GROUP_CRITERIA, RSC.TIME_BEGIN, RSC.TIME_END, RSC.TIME_CREATE, RSC.KOEFFICIENT FROM SUMMAR.REG_STATE_CRITERIA AS RSC INNER JOIN ( SELECT ID_CRITERIA, MAX(TIME_BEGIN) AS TIME_BEGIN FROM ( SELECT DISTINCT ID_CRITERIA, TIME_BEGIN FROM SUMMAR.REG_STATE_CRITERIA WHERE TIME_BEGIN < PMAX_TIME AND TIME_END > PMAX_TIME ) AS SL GROUP BY ID_CRITERIA ) AS MAX_SLICE ON RSC.ID_CRITERIA = MAX_SLICE.ID_CRITERIA AND RSC.TIME_BEGIN = MAX_SLICE.TIME_BEGIN ;
Maintenant, nous recevrons à la même date les valeurs de tous les critères pour toutes les branches CREATE OR REPLACE FUNCTION SUMMAR.SLICE_VALUE_ACTUAL_ALL_CRITERIA_ALL_DEPART_WITH_NAMES ( PMAX_TIME TIMESTAMP ) RETURNS TABLE ( ID_CRITERIA BIGINT, ID_GROUP_CRITERIA BIGINT, ID_DEPART BIGINT, DATE_REG TIMESTAMP, PERCENT DECIMAL(15, 2), VAL DECIMAL(15, 2), KOEFFICIENT DECIMAL(15, 2), CRITERIA_NAME CLOB, CRITERIA_CODE VARCHAR(255), GROUP_CRITERIA_NAME CLOB, GROUP_CRITERIA_CODE VARCHAR(255), DEPART_NAME CLOB, DEPART_CODE VARCHAR(255), DEPART_CODE_INT INT ) LANGUAGE SQL RETURN SELECT CDEP.ID_CRITERIA, COALESCE(VALS.ID_GROUP_CRITERIA, 0) AS ID_GROUP_CRITERIA, CDEP.ID_DEPART, VALS.DATE_REG, COALESCE(VALS.PERCENT, 0.0) AS PERCENT, COALESCE(VALS.VAL, 0.0) AS VAL, COALESCE(VALS.KOEFFICIENT, 0.0) AS KOEFFICIENT, CDEP.CRITERIA_NAME, CDEP.CRITERIA_CODE, COALESCE(VALS.GROUP_CRITERIA_NAME, '') AS GROUP_CRITERIA_NAME, COALESCE(VALS.GROUP_CRITERIA_CODE, '') AS GROUP_CRITERIA_CODE, CDEP.DEPART_NAME, CDEP.DEPART_CODE, CDEP.DEPART_CODE_INT FROM ( SELECT CCRT.ID AS ID_CRITERIA, CCRT."NAME" AS CRITERIA_NAME, CCRT.CODE AS CRITERIA_CODE, CDEP.ID AS ID_DEPART, CDEP."NAME" AS DEPART_NAME, CDEP.CODE AS DEPART_CODE, CAST (CDEP.CODE AS INT) AS DEPART_CODE_INT FROM SUMMAR.CLS_DEPART AS CDEP, ( SELECT * FROM SUMMAR.CLS_CRITERIA AS CC INNER JOIN TABLE(SUMMAR.SLICE_STATE_ALL_CRITERIA (PMAX_TIME)) AS ACTC ON CC.ID = ACTC.ID_CRITERIA WHERE CC.IS_DELETED = 0 ) AS CCRT WHERE CDEP.IS_DELETED = 0 ) AS CDEP LEFT JOIN ( SELECT VALS.ID_CRITERIA, VALS.ID_GROUP_CRITERIA, VALS.ID_DEPART, VALS.DATE_REG, VALS.PERCENT, VALS.VAL, VALS.KOEFFICIENT, CGRT."NAME" AS GROUP_CRITERIA_NAME, CGRT.CODE AS GROUP_CRITERIA_CODE FROM TABLE(SUMMAR.SLICE_VALUE_ACTUAL_ALL_CRITERIA (PMAX_TIME)) AS VALS INNER JOIN SUMMAR.CLS_GROUP_CRITERIA AS CGRT ON VALS.ID_GROUP_CRITERIA = CGRT.ID ) as VALS ON CDEP.ID_DEPART = VALS.ID_DEPART AND CDEP.ID_CRITERIA = VALS.ID_CRITERIA ;
Dans la requête finale, nous numérotons les valeurs des indicateurs, les classons et trouvons le minimum et le maximum, cela sera nécessaire pour calculer les places plus tard SELECT ROW_NUMBER() OVER() AS ID_NUM, RANK() OVER( PARTITION BY ID_CRITERIA ORDER BY VAL DESC ) AS RATING, CASE WHEN MAX(RANK() OVER( PARTITION BY ID_CRITERIA ORDER BY VAL DESC ) ) OVER() = RANK() OVER( PARTITION BY ID_CRITERIA ORDER BY VAL DESC ) THEN 1 ELSE 0 END AS MAX_RATING, CASE WHEN MIN(RANK() OVER( PARTITION BY ID_CRITERIA ORDER BY VAL DESC ) ) OVER() = RANK() OVER( PARTITION BY ID_CRITERIA ORDER BY VAL DESC ) THEN 1 ELSE 0 END AS MIN_RATING, VALS.* FROM TABLE(SUMMAR.SLICE_VALUE_ACTUAL_ALL_CRITERIA_ALL_DEPART_WITH_NAMES (?)) AS VALS ORDER BY GROUP_CRITERIA_CODE, CRITERIA_CODE, DEPART_CODE_INT
Le transfert d'une partie de la logique métier vers le SGBD est tout à fait justifié, notamment lorsqu'il s'agit de préparer des données pour différents rapports. Il s'avère que les opérations peuvent être écrites sous une forme plus concise et naturelle; en Java, de telles manipulations avec les données nécessiteraient une plus grande quantité de code et quelques efforts pour la structurer. Bien que les opérations relativement complexes ou non triviales soient toujours plus faciles à programmer en Java. Par conséquent, dans notre application, en ce qui concerne l'échantillonnage des données, une approche est utilisée dans laquelle la connexion des ensembles de données, des conditions d'écrêtage et de certaines opérations qui peuvent être effectuées par des fonctions de fenêtre sont effectuées dans des fonctions et des procédures stockées, et une logique plus complexe est implémentée dans l'application.
App
Comme je l'ai dit, OpenXava a été utilisé pour implémenter l'application. Pour obtenir une interface et un CRUD typiques, vous devez effectuer certaines actions.
Pour commencer, dans web.xml, vous devez décrire le filtre et le servlet de l'addon qui navigue dans l'application:
web.xml <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <filter> <filter-name>naviox</filter-name> <filter-class>com.openxava.naviox.web.NaviOXFilter</filter-class> </filter> <filter-mapping> <filter-name>naviox</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>naviox</filter-name> <url-pattern>/modules/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <filter-mapping> <filter-name>naviox</filter-name> <servlet-name>naviox</servlet-name> </filter-mapping> <filter-mapping> <filter-name>naviox</filter-name> <servlet-name>module</servlet-name> </filter-mapping> <servlet> <servlet-name>naviox</servlet-name> <servlet-class>com.openxava.naviox.web.NaviOXServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>naviox</servlet-name> <url-pattern>/m/*</url-pattern> </servlet-mapping> </web-app>
Ensuite, dans le fichier controllers.xml, nous définissons les contrôleurs utilisés dans l'application. Dans notre cas, le plus simple suffit:
controllers.xml <controllers> <controller name="Typical_View"> <extends controller="Navigation"/> <extends controller="CRUD"/> <extends controller="ExtendedPrint"/> </controller> </controllers>
Le contrôleur ci-dessus combine les fonctions des contrôleurs inclus dans OpenXava par défaut, dont les fonctions sont faciles à deviner à partir des noms.
Et enfin, dans le fichier application.xml, nous connecterons le contrôleur créé et le modèle. Quelque chose comme ça:
application.xml <application name="summar"> <module name="RegValueCriteria"> <model name="RegValueCriteria"/> <controller name="Typical_View"/> </module> </application>
Comme mentionné ci-dessus, l'application est basée sur les composants métier qui composent le modèle d'application. Par exemple, considérez le composant RegValueCriteria associé au contrôleur dans application.xml. Ce composant décrit la valeur du critère pour la branche (par souci de concision, seule la description des champs de classe est laissée, et je vais omettre des méthodes telles que les getters et les setters):
Classe de composants @Entity @Table(name = "REG_VALUE_CRITERIA", catalog = "", schema = "SUMMAR") @XmlRootElement @Views({ @View(members = "idCriteria [idCriteria];" + "idGroupCriteria [idGroupCriteria];" + "idDepart [idDepart];" + "data [dateReg, percent, val]"), @View(name="massReg", members = "idDepart.name, percent, val") }) @Tab(properties= "idDepart.name, idCriteria.name, idGroupCriteria.name, dateReg, percent, val" ) public class RegValueCriteria implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") private Long id; @Basic(optional = false) @NotNull @Column(name = "DATE_REG") @Temporal(TemporalType.TIMESTAMP) @DefaultValueCalculator(CurrentDateCalculator.class) @Stereotype("DATETIME") private Date dateReg; @Column(name = "PERCENT") @OnChange(OnChangePercentAction.class) private BigDecimal percent; @Column(name = "VAL") private BigDecimal val; @JoinColumn(name = "ID_CRITERIA", referencedColumnName = "ID") @ManyToOne(optional = false) @DescriptionsList( descriptionProperties="name" ) @OnChange(OnChangeClsCriteriaAction.class) private ClsCriteria idCriteria; @JoinColumn(name = "ID_GROUP_CRITERIA", referencedColumnName = "ID") @ManyToOne(optional = false) @DescriptionsList( descriptionProperties="name" ) private ClsGroupCriteria idGroupCriteria; @JoinColumn(name = "ID_DEPART", referencedColumnName = "ID") @ManyToOne(optional = false) @DescriptionsList( descriptionProperties="name" ) private ClsDepart idDepart; }
En plus des annotations JPA habituelles. Vous pouvez également remarquer des annotations OpenXava. Ils devraient être examinés plus en détail.
L'
@View
permet de contrôler le format de présentation des champs de classe et, à l'aide d'une syntaxe spéciale sous forme de crochets, les champs peuvent être regroupés et disposés horizontalement et verticalement à l'aide de symboles
,
et
;
. Si plusieurs mappages doivent être spécifiés pour un seul composant, les annotations
@View
sont regroupées à l'aide d'annotations
@View
. Dans notre exemple, les propriétés ont été organisées comme suit:
@View(members = "idCriteria [idCriteria];" + "idGroupCriteria [idGroupCriteria];" + "idDepart [idDepart];" + "data [dateReg, percent, val]")
Et cela ressemble à ceci:

Sans prétention, mais avec un minimum d'effort. Cependant, il existe des moyens de «faire revivre» un peu la forme.
Pour que la date d'enregistrement soit remplie lors de la création du composant, l'annotation
@DefaultValueCalculator
est
@DefaultValueCalculator
, qui appelle un objet calculateur spécial. Ici, une calculatrice d'OpenXava elle-même est utilisée, mais vous pouvez également en faire une personnalisée. L'annotation
@Stereotype
est utilisée pour afficher la date à l'aide du contrôle approprié.
Pour configurer des listes déroulantes contenant des objets associés, l'annotation
@DescriptionsList
est
@DescriptionsList
, dans laquelle vous pouvez spécifier quelle propriété sera affichée dans la liste.
À l'aide d'annotations, vous pouvez implémenter une logique métier du formulaire lui-même. Par exemple, pour que lorsque le pourcentage change, la valeur est calculée automatiquement en tenant compte du coefficient du critère, vous pouvez appliquer l'annotation
@OnChange
pour le champ
BigDecimal val
. Pour que l'annotation
@OnChange
fonctionne, elle doit pointer vers la classe implémentant l'interface
OnChangePropertyBaseAction
. Une seule méthode
execute()
doit être implémentée dans la classe, dans laquelle les données d'entrée sont extraites de la vue, le calcul est effectué et la valeur calculée est réécrite dans la vue:
Classe enfant OnChangePropertyBaseAction public class OnChangePercentAction extends OnChangePropertyBaseAction{ @Override public void execute() throws Exception { BigDecimal percent = (BigDecimal)getNewValue(); if (percent != null){ Map value = (Map)getView().getValue("idCriteria"); if (value != null){ Long idCriteria = (Long)value.get("id"); Query query = XPersistence.getManager().createNativeQuery( "SELECT KOEFFICIENT FROM SUMMAR.SLCLA_STATE_CRITERIA WHERE ID_CRITERIA = ?"); query.setParameter(1, idCriteria); List<?> list = query.getResultList(); if (list != null && !list.isEmpty()){ BigDecimal koef = (BigDecimal) list.get(0); BigDecimal vl = koef.multiply(percent); getView().setValue("val", vl); } } } } }
Pour une présentation tabulaire des données, l'annotation
@Tab
est
@Tab
, ce qui vous permet de répertorier les propriétés des objets qui seront affichées dans une représentation tabulaire. Dans notre exemple, l'annotation a été structurée comme suit:
@Tab(properties= "idDepart.name, idCriteria.name, idGroupCriteria.name, dateReg, percent, val" )
Il ressemblera à ceci

Heureux de la disponibilité des filtres, de la pagination et de l'exportation hors de la boîte, cependant, de nombreux détails nécessitent un raffinement avec un fichier.
Le travail avec d'autres composants est construit de manière similaire. L'utilisation d'OpenXava a considérablement réduit les coûts de main-d'œuvre pour la mise en œuvre des fonctions CRUD et la plupart de l'interface utilisateur. L'utilisation d'actions à partir de contrôleurs et d'annotations prédéfinis pour créer des formulaires permet de gagner beaucoup de temps si vous ne trouvez pas de problème avec les détails et essayez d'implémenter quelque chose de plus compliqué que le formulaire d'entrée avec plusieurs événements. Bien que cela puisse être le cas dans l'expérience.
De quoi il s'agissait
Rappelez-vous ce que l'application était en place? Oui, afin que la plaque avec les indicateurs ne soit pas héroïquement réduite dans Excel, mais créée automatiquement en fonction des données saisies. Dans la fenêtre du navigateur, le tableau croisé dynamique a commencé à ressembler à ceci:


Je ne donnerai pas de détails sur la mise en œuvre, car tout n'est pas très bon avec elle et le mélange de JSP avec le HTML généré lors de la demande de données n'est pas quelque chose à partager avec le grand public. Dans le même temps, l'échantillonnage des données lui-même a été démontré ci-dessus.
Cependant, je veux m'attarder sur un détail intéressant. Lorsque les exigences de la demande ont été recueillies, la direction souhaitait vraiment que, en plus du rapport de synthèse, les valeurs d'un indicateur individuel puissent être affichées sous la forme d'une carte de la région, divisée en régions avec une indication de l'emplacement et du score de la branche correspondante. Qui a reconnu la région sur la carte - bravo =)

D'une part, l'exigence était facultative, mais d'autre part, l'image promettait d'être visuelle, et du point de vue de la mise en œuvre, il était intéressant d'essayer. Après réflexion, l'idée est venue de trouver l'image de la région au format SVG et d'en faire un modèle XSLT.
Le modèle résultant est facilement rempli de données, puis converti en PNG.
Tout d'abord, en utilisant la requête décrite ci-dessus, les données sont échantillonnées, les données reçues sont converties en un objet de cette classe:
Classes pour la sortie de données sur une carte @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "VisualisedValuesCriteria") public class VisualisedValuesCriteria { private XMLGregorianCalendar date; private String criteriaName; private String koefficient; private List<VisualizedValueCriteria> departValues; public XMLGregorianCalendar getDate() { return date; } public void setDate(XMLGregorianCalendar date) { this.date = date; } public String getCriteriaName() { return criteriaName; } public void setCriteriaName(String criteriaName) { this.criteriaName = criteriaName; } public String getKoefficient() { return koefficient; } public void setKoefficient(String koefficient) { this.koefficient = koefficient; } public List<VisualizedValueCriteria> getDepartValues() { if (departValues == null){ departValues = new ArrayList<>(); } return departValues; } public void setDepartValues(List<VisualizedValueCriteria> departValues) { this.departValues = departValues; } } @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class VisualizedValueCriteria { private String departName; private String departCode; private String value; private String percent; private String colorCode; public String getDepartName() { return departName; } public void setDepartName(String departName) { this.departName = departName; } public String getDepartCode() { return departCode; } public void setDepartCode(String departCode) { this.departCode = departCode; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getPercent() { return percent; } public void setPercent(String percent) { this.percent = percent; } public String getColorCode() { return colorCode; } public void setColorCode(String colorCode) { this.colorCode = colorCode; } } }
Ensuite, convertissez l'objet en XML;
Conversion Private String marshal (VisualisedValuesCriteria obj){ final Marshaller marshaller = JAXBContext.newInstance(VisualisedValuesCriteria.class).createMarshaller(); marshaller.setEventHandler(new DefaultValidationEventHandler()); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); StringWriter writer = new StringWriter(); marshaller.marshal(obj, writer); return writer.toString(); }
Maintenant, prenons le XML résultant, le modèle XSLT préparé, appliquons la conversion et obtenons svg dans la sortie:
Obtenez svg String xml = marshal(obj); TransformerFactory factory = TransformerFactory.newInstance(); FileInputStream xsltFis = new FileInputStream("C:\\TEMP\\map_xsl.svg"); InputStreamReader xsltIn = new InputStreamReader(xsltFis, "UTF-8"); Source xslt = new StreamSource(xsltIn); Transformer transformer = factory.newTransformer(xslt); InputStream xmlIn = new ByteArrayInputStream( xml.getBytes( "UTF-8" ) ); Source text = new StreamSource(xmlIn); String filename = "map" + System.currentTimeMillis() + ".svg"; String filePath = "C:\\TEMP\\" + filename; transformer.transform(text, new StreamResult(new File(filePath)));
En principe, nous pourrions nous arrêter ici, les navigateurs affichent SVG sans aucun problème. Mais les rapports décrits ont également été obtenus via le bot Telegram, donc SVG doit être converti en un format tel que JPEG ou PNG. Pour ce faire, utilisez
Apache BatikConvertir en PNG private String convertToPNG(final String svg, final String filename, final String filePath){ String png = filePath + filename + ".png"; try { PNGTranscoder trancoder = new PNGTranscoder(); String svgURI = new File(svg).toURL().toString(); TranscoderInput input = new TranscoderInput(svgURI); OutputStream ostream = new FileOutputStream(png); TranscoderOutput output = new TranscoderOutput(ostream); trancoder.transcode(input, output); ostream.flush(); ostream.close(); return filename + ".png"; } catch (MalformedURLException ex) { Logger.getLogger(ActualCriteriaValueGraphicServlet.class.getName()).log(Level.SEVERE, null, ex); } catch (FileNotFoundException | TranscoderException ex) { Logger.getLogger(ActualCriteriaValueGraphicServlet.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(ActualCriteriaValueGraphicServlet.class.getName()).log(Level.SEVERE, null, ex); } return null; }
Le rapport sous forme de carte est prêt. Il peut également être consulté via le navigateur et demandé au robot télégramme. À mon avis, pas mal.
Conclusion
À l'heure fixée, nous avions le temps et déjà en mars 2017, des indicateurs de performance au lieu d'Excel ont commencé à être régulièrement entrés dans le système créé. D'une part, le problème maximum n'a pas été résolu; la partie la plus difficile du calcul des indicateurs se fait manuellement. Mais d'un autre côté, la mise en œuvre de ces calculs comportait des risques de raffinement constant. De plus, même l'interface simple créée pour la collecte de données a supprimé un grand nombre de questions avec des changements constants, un contrôle de version et un résumé de la feuille de calcul Excel. Un grand nombre de travaux manuels, de contrôles et de vérifications croisées ont été supprimés.
Il est impossible de ne pas dire que l'interface sur Open Xav n'était pas trop agréable aux utilisateurs. Au début, ses caractéristiques posaient de nombreuses questions. À un moment donné, les utilisateurs ont commencé à se plaindre qu'il avait fallu trop de temps pour entrer des données et, en général, «nous voulons, comme dans Excel, seulement un programme». J'ai même dû surveiller la vitesse d'entrée en fonction des données sur le moment de la création des enregistrements. Cette surveillance a montré que même dans les cas les plus graves, les utilisateurs ne passaient pas plus de 15 minutes en entrée, mais se situaient généralement entre 5 et 7, malgré le fait qu'ils devaient saisir des données sur 22 succursales. Ces indicateurs semblent tout à fait acceptables.
Cependant, je veux noter deux choses:
- Open Xava s'est avéré être un bon outil pour créer rapidement une interface. Je dirais même une interface prototype. Son avantage incontestable est également la régularité générale et la régularité. Tous les formulaires de l'application sont créés selon des principes uniformes, ce qui permet au développeur de ne pas proposer des vélos là où il n'en a pas besoin, mais à l'utilisateur de traiter des jeux de formulaires standard. Cependant, les tentatives de mise en œuvre d'une logique plus complexe ou de modification des contrôles pour nous-mêmes nous ont conduits à un certain nombre de problèmes auxquels nous n'avons pas pu faire face dans le temps imparti. Très probablement, nous ne comprenions tout simplement pas, et l'objectif était de créer une interface CRUD simple avec un minimum d'effort. Pour ma part, je conclus qu'Open Xava est un outil intéressant dans lequel il est facile de faire des choses simples, mais si vous avez besoin de faire quelque chose de compliqué, je préfère passer plus de temps à créer la partie client en utilisant ExtJS ou React, mais avoir plus de flexibilité .
- Même les utilisateurs confiants prennent du mal avec les nouvelles interfaces. Ce n'est bien sûr pas un secret. À mon avis, cela est principalement dû à un manque de compréhension de la nature systémique de nombreuses interfaces. Pour beaucoup, toute application possède un ensemble d'écrans, chacun étant unique et fonctionnant selon ses propres principes inconnus: par exemple, il existe un formulaire avec une liste d'objets / lignes (formulaire de liste), mais pour de nombreux utilisateurs, il n'est pas du tout évident que chacun de ces formulaires dans l'application puisse avoir filtrage uniforme, pagination, fonctions de tri et généralement le même comportement, complété par des fonctions spécifiques. Ceci est aggravé par le fait qu'un grand nombre de logiciels d'entreprise est une pile presque spontanée de boutons et de formulaires, assaisonnée d'une documentation indistincte dans le style de «Cliquez sur ce bouton». De ce point de vue, les interfaces créées par les mêmes développeurs utilisateurs de la discipline Open Xav, créent plus d'ordre dans la tête. Certes, avec cette approche, les boutons magiques ne disparaissent nulle part, mais deviennent soigneusement disposés en formes.
Si nous parlons des avantages pour l'organisation dans son ensemble, après l'introduction de la demande, comme prévu, le degré de contrôle a augmenté. Les responsables de terrain ont reçu un outil de suivi des indicateurs, avec lequel ils peuvent influencer le travail dans un mode plus opérationnel, comparer les indicateurs pour différentes périodes, sans se confondre avec le flux des fichiers envoyés. L'intérêt des dirigeants pour le suivi constant et vigilant de leur branche a été découvert par nous de manière très intéressante: en parcourant les logs du bot Telegram, nous avons remarqué que certains rapports étaient reçus à 4 ou 5 heures du matin. Vous pouvez immédiatement voir qui est intéressé par le travail.
C’est tout. Je serais reconnaissant pour la rétroaction constructive!