So automatisieren Sie die Erfassung von KPI pro Monat und lassen Benutzer fast zufrieden sein

In vielen Organisationen wird die Einheitenbewertung mithilfe von KPI (Key Performance Indicators) durchgeführt. In der Organisation, in der ich arbeite, wird ein solches System als „System von Leistungsindikatoren“ bezeichnet. In diesem Artikel möchte ich darüber sprechen, wie wir es geschafft haben, einen Teil der Arbeit mit Indikatoren innerhalb eines Monats zu automatisieren. Bei alledem waren unsere Arbeitskosten nicht die größten, aber gleichzeitig haben wir versucht, eine langjährige Wunschliste umzusetzen. In meiner Geschichte wird es keine Hype-Technologien oder Enthüllungen geben (schließlich ist die Provinzentwicklung hart), aber es wird einige Skizzen zu diesem Thema geben, die helfen, zu verstehen, wie wir angefangen haben, was wir getan haben und welche Gedanken wir von der Entwicklung bekommen haben. Wenn Sie sich noch nicht gelangweilt haben, bitte ich um eine Katze.

Hintergrund


Bereits 2006 haben wir ein System von Leistungsindikatoren eingeführt: Es wurden fünfzehn Bewertungskriterien entwickelt, eine Methode zu deren Berechnung erstellt und eine vierteljährliche Häufigkeit zur Berechnung dieser Indikatoren festgelegt. Die Bewertung wurde für 22 Zweigstellen der Organisation in allen Bereichen unserer Region durchgeführt. Und damit Indikatoren mit großer Begeisterung erreicht werden konnten, war ein Bonus an sie gebunden - je höher die Summe der Indikatoren, desto höher der Platz in der Bewertung, desto höher der Platz in der Bewertung, desto höher die Prämie usw. jedes Quartal und jedes Jahr.

Im Laufe der Zeit änderte sich die Zusammensetzung der Kriterien: Jedes Quartal wurden neue hinzugefügt oder alte ausgeschlossen. Auf dem Höhepunkt, etwa 2016, überstieg die Anzahl der Indikatoren vierzig, und jetzt sind es nur noch vierzehn.

Während dieser ganzen Zeit war der Prozess ihrer Berechnung jedoch der gleiche. Jedes Kriterium wird von der zuständigen Einheit der übergeordneten Organisation nach einem speziell für dieses Kriterium genehmigten Algorithmus berechnet. Die Berechnung kann sowohl nach einer einfachen als auch nach einer Reihe komplexer Formeln durchgeführt werden. Möglicherweise müssen Daten von mehreren Systemen erfasst werden, während sich der Algorithmus sehr wahrscheinlich häufig ändert. Und dann ist schon alles viel einfacher: Der prozentual berechnete Indikator wird mit seinem Koeffizienten multipliziert, so wird ein Punkt durch das Kriterium erhalten, dann werden die Punkte eingestuft und jede Einheit findet gemäß dem Punkt statt. Das Gleiche gilt für die Summe der Punkte zur Berechnung des endgültigen Platzes.

Excel wurde von Anfang an verwendet, um alles oben Beschriebene zu berücksichtigen und zu berechnen. Über viele Jahre hinweg wurde der endgültige Kriteriensatz und die weitere Berechnung von Punkten und Orten in einer schönen Platte durchgeführt, von der ein Teil in der Abbildung dargestellt ist.



Ich stelle nur fest, dass in diesem schönen Tablet die meisten Lautsprecher einfach versteckt sind, aber in Wirklichkeit sah es im 4. Quartal 2016 so aus



Stimmen Sie zu, eine solche Anzahl von Indikatoren und ein solches Volumen der Tabelle tragen nicht zu ihrer Einfachheit bei. Zusätzlich wurde jedes der Kriterien separat berechnet, und Mädchen aus der allgemeinen Abteilung erstellten diese Übersichtstabelle mit ihren Händen. In Anbetracht der Tatsache, dass sich nicht nur die Zusammensetzung der Kriterien geändert hat, sondern auch die Berechnungen (aus vielen Gründen) mehrmals wiederholt werden konnten, waren die letzten Tage eines jeden Quartals, gelinde gesagt, nicht glücklich. Ich musste die Daten in der Tabelle ständig ändern, überprüfen und überprüfen: Auf diese Weise vergessen Sie, der Summe der Punkte eine neue Spalte hinzuzufügen, oder Sie aktualisieren den Koeffizienten nicht, und für einige Personen wird der Platz niedriger, und damit ist der Bonus geringer. Daher begann nach der heldenhaften Verkleinerung der Tabelle eine nicht weniger heldenhafte Überprüfung, die von allen, die jedes Kriterium berücksichtigten, von Managern und sogar von einem organisatorisch zugewiesenen IT-Spezialisten, überprüft und vereinbart wurde. Angesichts der Tatsache, dass alle Arbeiten manuell ausgeführt wurden und die Berechnung der Indikatoren für die Pivot-Tabelle an die E-Mail gesendet wurde, gab es häufig Situationen, in denen die Pivot-Tabelle Fehler in Formeln und Daten irrelevanter Versionen enthielt. Der Hauptmann der Beweise berichtet, dass der Überprüfungsprozess nach jedem festgestellten Fehler neu gestartet wurde.
Wenn alle Indikatoren berechnet sind, wird das Tablet genehmigt und an die Filialen gesendet. Dort wurde es in den Filialen doppelt überprüft: Was ist, wenn das heimtückische Excel glaubt, dass etwas nicht stimmt? Ich denke, es ist offensichtlich, dass sich die Erstellung eines Jahresberichts über die Kriterien oder die Analyse historischer Daten als nicht weniger aufregende Aufgabe herausstellte.

Aus Managementsicht ist ein solches System jedoch sehr effektiv und ermöglicht es, bei Bedarf durchhängende Arbeitsbereiche zu straffen, zu überwachen und eine Vorstellung davon zu haben, was in den einzelnen Niederlassungen in einem bestimmten Arbeitsbereich geschieht. Darüber hinaus war der nach den Kriterien berechnete Standort der Niederlassung ein integraler Indikator.

System muss sich ändern


Die Praxis hat die Notwendigkeit und Bedeutung eines Bewertungssystems bewiesen. Anfang 2017 wurde klar, dass die vierteljährliche Berechnung der Kriterien eine Bewertung der geleisteten Arbeit ermöglichte, die Überwachung jedoch eine Schwäche zuließ. Zu lange Zeitspanne. In diesem Zusammenhang wurde beschlossen, die Berechnung der Indikatoren alle zwei Wochen durchzuführen und die Ergebnisse vierteljährlich zusammenzufassen. Die Erhöhung der Berechnungshäufigkeit ermöglichte es den Filialleitern, schnell auf Änderungen zu reagieren und die Kontrolle über die Prozesse in ihren Einheiten zu verbessern.

Aber nur in der Mutterorganisation waren nur wenige mit der Aussicht zufrieden, den oben beschriebenen Datenerfassungsprozess nicht vierteljährlich, sondern alle zwei Wochen durchzuführen. Da es nicht schwer zu erraten ist, haben sie beschlossen, den Prozess zu automatisieren. Die Fristen erwiesen sich als eng: Vom Zeitpunkt der Entscheidung, auf eine neue Berechnungshäufigkeit umzusteigen, bis zum eigentlichen Übergang sollte nur ein Monat vergangen sein. Für diesen Zeitraum war es sehr wünschenswert, etwas Besseres als eine Reihe von Excel für Code und Berechnung sowie E-Mails für die Erfassung und Benachrichtigung zu entwickeln.

Anfangs gab es einige heftige Diskussionen darüber, was wirklich, wirklich automatisch getan werden musste und die Berechnung der Indikatoren selbst unter Berücksichtigung aller ihrer Formeln und Datenquellen. Angesichts der engen Fristen, der Komplexität dieser Funktionen und der Notwendigkeit ihrer ständigen Wartung im aktuellen Zustand konnten jedoch die folgenden Anforderungen an das System erfüllt werden:

  • Ein Referenzhandbuch mit Kriterien sollte aufbewahrt werden, um die Geschichte ihrer Änderung zu bewahren.
  • Es sollte möglich sein, die berechneten Indikatoren sowie deren Umrechnung in Punkte wie auf derselben Platte einzugeben und zu speichern.
  • Basierend auf den Werten der Indikatoren sollte ein Bericht erstellt werden, der allen interessierten Parteien zugänglich ist.
  • All dies sollte natürlich mit einem Webinterface versehen sein.

Die Funktionalität ist sehr klein, aber nicht viel Zeit.

Entwicklungsstart


Ich war immer von der schnellen Entwicklung und der automatischen Generierung von Schnittstellen angezogen. In Fällen, in denen die Implementierung der CRUD-Funktionalität erforderlich ist, erscheint die Idee, wann die Schnittstelle und ein Teil der Geschäftslogik sozusagen sofort bereitgestellt werden, sehr verlockend. Natürlich wird die Schnittstelle eine unprätentiöse und ungeschickte Logik sein, aber für viele Aufgaben ist dies ausreichend.

Angetrieben von diesen Ideen beschloss ich, so etwas auszuprobieren. Es gibt Spring Roo, Kuba und andere interessante Tools, aber die Wahl fiel auf OpenXava. Erstens, nachdem ich bereits eine sehr einfache Anwendung darauf gemacht hatte und zufrieden war, und zweitens passte dieses Framework zu dieser Zeit recht erfolgreich in unseren technologischen Stack. Darüber hinaus ist es sehr erfreulich, ein kurzes Tutorial auf Russisch zu haben.

Eine kurze Beschreibung der Funktionen und Möglichkeiten von OpenXava finden Sie hier . OpenXava ist ein Framework, das den automatischen Aufbau einer Webschnittstelle implementiert, die in eine auf JPA basierende Datenbank integriert ist, und Anmerkungen zur Beschreibung von Visualisierungsregeln verwendet. Die Anwendung basiert auf Geschäftskomponenten - Java-Klassen, die die zum Erstellen von Anwendungen erforderlichen Informationen enthalten. Zu diesen Informationen gehören Datenstruktur, Validatoren, gültige Darstellungen und die Zuordnung zu Datenbanktabellen. Vorgänge an Geschäftskomponenten werden über Controller ausgeführt, die CRUD aus einer Box erstellen, suchen, in PDF exportieren usw. können. Die OpenXava-Anwendung bietet eine Reihe von Modulen. Ein Modul ordnet eine Geschäftskomponente einem oder mehreren Controllern zu. Für jede Geschäftskomponente definierte Darstellungen werden zum Anzeigen der Schnittstelle verwendet. Nichts ungewöhnliches, MVC mit etwas eigener Atmosphäre.

Datenspeicherung


In den meisten Anwendungen verwenden wir IBM DB2 DBMS. Es wurde eine kleine Datenbank erstellt, in der die Nachschlagewerke von Kriteriengruppen und Kriterien, anhand derer die Zweige bewertet werden, das Verzeichnis der Zweige und eine Platte gespeichert werden, in die die berechneten Werte der Kriterien eingegeben werden. Für jedes Kriterium wird zu einem bestimmten Zeitpunkt ein Koeffizient zugewiesen, der bei der Bewertung verwendet wird. Jeder Zweig erhält für jedes Kriterium eine Bewertung auch für ein bestimmtes Datum. Daten zu den Werten der Kriterien werden historisch gespeichert, dh die für ein Datum relevanten Daten sind diejenigen, die am nächstgelegenen Datum in der Vergangenheit eingegeben wurden. Dieser Ansatz, der von den Informationsregistern von 1C: Enterprise inspiriert wurde, scheint mir bequem genug zu sein: Es gibt eine Historie, und die Probleme beim Bearbeiten / Löschen sind nicht besonders hoch.

Datenbankstruktur
CREATE 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 ); 


Um den begehrten Bericht über die Leistung von Zweigen aus der Datenbank zu erhalten, wurden gespeicherte Funktionen erstellt, deren Ergebnisse bereits der Java-Klasse zugeordnet sind. Funktionen sehen ungefähr so ​​aus.
Zuerst erhalten wir alle für das Datum gültigen Kriterien sowie die für dieses Datum relevanten Kriterienkoeffizienten
 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 ; 


Jetzt erhalten wir am selben Tag die Werte aller Kriterien für alle Branchen
 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 ; 


In der letzten Abfrage nummerieren wir die Werte der Indikatoren, ordnen sie und ermitteln das Minimum und Maximum. Dies wird benötigt, um Orte später zu berechnen
 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 


Die Übertragung eines Teils der Geschäftslogik in das DBMS ist durchaus gerechtfertigt, insbesondere wenn es darum geht, Daten für verschiedene Berichte vorzubereiten. Es stellt sich heraus, dass die Operationen in einer präziseren und natürlicheren Form geschrieben werden können. In Java würden solche Manipulationen mit den Daten eine größere Menge an Code und einige Anstrengungen erfordern, um sie zu strukturieren. Obwohl relativ komplexe oder nicht triviale Operationen in Java immer noch einfacher zu programmieren sind. Daher wird in unserer Anwendung in Bezug auf die Datenabtastung ein Ansatz verwendet, bei dem die Verbindung von Datensätzen, Beschneidungsbedingungen und einige Operationen, die von Fensterfunktionen ausgeführt werden können, in gespeicherten Funktionen und Prozeduren ausgeführt werden und eine komplexere Logik in der Anwendung implementiert wird.

App


Wie gesagt, OpenXava wurde verwendet, um die Anwendung zu implementieren. Um eine typische Schnittstelle und CRUD sofort einsatzbereit zu machen, müssen Sie einige Aktionen ausführen.
Zunächst müssen Sie in web.xml den Filter und das Servlet des Addons beschreiben, das in der Anwendung navigiert:

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> 


Als Nächstes definieren wir in der Datei controller.xml die in der Anwendung verwendeten Controller. In unserem Fall ist das Einfachste genug:

controller.xml
 <controllers> <controller name="Typical_View"> <extends controller="Navigation"/> <extends controller="CRUD"/> <extends controller="ExtendedPrint"/> </controller> </controllers> 


Der obige Controller kombiniert standardmäßig die Funktionen der in OpenXava enthaltenen Controller, deren Funktionen anhand der Namen leicht zu erraten sind.

Und schließlich verbinden wir in der Datei application.xml den erstellten Controller und das Modell. Ungefähr so:

application.xml
 <application name="summar"> <module name="RegValueCriteria"> <model name="RegValueCriteria"/> <controller name="Typical_View"/> </module> </application> 


Wie oben erwähnt, basiert die Anwendung auf den Geschäftskomponenten, aus denen das Anwendungsmodell besteht. Betrachten Sie beispielsweise die RegValueCriteria-Komponente, die dem Controller in application.xml zugeordnet ist. Diese Komponente beschreibt den Wert des Kriteriums für den Zweig (der Kürze halber bleibt nur die Beschreibung der Klassenfelder übrig, und ich werde Methoden wie Getter und Setter weglassen):

Komponentenklasse
 @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; } 


Zusätzlich zu den üblichen JPA-Anmerkungen. Sie können auch OpenXava-Anmerkungen bemerken. Sie sollten genauer betrachtet werden.

Mit der @View können @View das Präsentationsformat von Klassenfeldern steuern. Mithilfe einer speziellen Syntax in Form von eckigen Klammern können Felder zu Gruppen zusammengefasst und mithilfe von Symbolen horizontal und vertikal angeordnet werden ; . Wenn für eine einzelne Komponente mehrere Zuordnungen angegeben werden müssen, werden @View Annotationen mithilfe von @View @View Annotationen gruppiert. In unserem Beispiel wurden die Eigenschaften wie folgt angeordnet:

 @View(members = "idCriteria [idCriteria];" + "idGroupCriteria [idGroupCriteria];" + "idDepart [idDepart];" + "data [dateReg, percent, val]") 

Und es sieht so aus:



Unprätentiös, aber mit minimalem Aufwand. Es gibt jedoch Möglichkeiten, die Form ein wenig wiederzubeleben.
Damit das Registrierungsdatum beim Erstellen der Komponente @DefaultValueCalculator wird, wird die Annotation @DefaultValueCalculator , die ein spezielles @DefaultValueCalculator aufruft. Hier wird ein Taschenrechner von OpenXava selbst verwendet, Sie können jedoch auch einen benutzerdefinierten erstellen. Die Annotation @Stereotype wird verwendet, um das Datum mit dem entsprechenden Steuerelement anzuzeigen.

Zum Konfigurieren von Dropdown-Listen mit verwandten Objekten wird die Annotation @DescriptionsList , in der Sie angeben können, welche Eigenschaft in der Liste angezeigt werden soll.

Mithilfe von Anmerkungen können Sie eine Geschäftslogik des Formulars selbst implementieren. Wenn sich beispielsweise der Prozentsatz ändert und der Wert automatisch unter Berücksichtigung des Koeffizienten des Kriteriums berechnet wird, können Sie die Annotation @OnChange für das Feld BigDecimal val anwenden. Damit die Annotation @OnChange funktioniert, muss sie auf die Klasse verweisen, die die OnChangePropertyBaseAction Schnittstelle implementiert. In der Klasse sollte eine einzelne execute() -Methode implementiert werden, in der die Eingabedaten aus der Ansicht entnommen, die Berechnung durchgeführt und der berechnete Wert in die Ansicht zurückgeschrieben werden:

Untergeordnete Klasse 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); } } } } } 


Für eine tabellarische Darstellung von Daten wird die Annotation @Tab , mit der Sie die Eigenschaften von Objekten @Tab , die in einer tabellarischen Darstellung angezeigt werden. In unserem Beispiel wurde die Anmerkung wie folgt umrahmt:

 @Tab(properties= "idDepart.name, idCriteria.name, idGroupCriteria.name, dateReg, percent, val" ) 

Es wird wie folgt aussehen



Erfreut über die Verfügbarkeit von Filtern, die Paginierung und den sofortigen Export. Viele Details müssen jedoch mit einer Datei verfeinert werden.

Die Arbeit mit anderen Komponenten ist ähnlich aufgebaut. Durch die Verwendung von OpenXava konnten die Arbeitskosten für die Implementierung von CRUD-Funktionen und den größten Teil der Benutzeroberfläche drastisch gesenkt werden. Die Verwendung von Aktionen von vordefinierten Controllern und Anmerkungen zum Erstellen von Formularen spart erheblich Zeit, wenn Sie nicht über die Details streiten und versuchen, etwas Komplizierteres als das Eingabeformular mit mehreren Ereignissen zu implementieren. Obwohl es in der Erfahrung der Fall sein kann.

Worum es ging


Erinnerst du dich, was die App vorhatte? Ja, damit die Platte mit den Indikatoren in Excel nicht heldenhaft reduziert wird, sondern automatisch anhand der eingegebenen Daten erstellt wird. Im Browserfenster sah die Pivot-Tabelle folgendermaßen aus:




Ich werde keine Details zur Implementierung geben, da nicht alles sehr gut damit ist und die Mischung aus JSP und HTML, die beim Anfordern von Daten generiert wird, nicht für die breite Öffentlichkeit zugänglich ist. Gleichzeitig wurde oben die Datenerfassung selbst demonstriert.

Ich möchte jedoch auf ein interessantes Detail eingehen. Als die Anforderungen für den Antrag erfasst wurden, wollte das Management wirklich, dass zusätzlich zum zusammenfassenden Bericht die Werte eines einzelnen Indikators in Form einer Karte der Region angezeigt werden, die in Regionen mit Angabe des Standorts und der Punktzahl der entsprechenden Niederlassung unterteilt ist. Wer hat die Region auf der Karte erkannt - gut gemacht =)



Einerseits war die Anforderung optional, andererseits versprach das Bild visuell zu sein, und aus Sicht der Implementierung war es interessant, es zu versuchen. Nach einigem Überlegen kam die Idee auf, das Bild der Region im SVG-Format zu finden und daraus eine XSLT-Vorlage zu erstellen.

Die resultierende Vorlage kann leicht mit Daten gefüllt und anschließend in PNG konvertiert werden.
Zunächst werden unter Verwendung der oben beschriebenen Abfrage Daten abgetastet und die empfangenen Daten in ein Objekt dieser Klasse konvertiert:

Klassen zur Ausgabe von Daten auf eine Karte
 @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; } } } 


Konvertieren Sie als Nächstes das Objekt in XML.

Konvertierung
 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(); } 


Nehmen wir nun das resultierende XML, die vorbereitete XSLT-Vorlage, wenden die Konvertierung an und erhalten svg in der Ausgabe:

Holen Sie sich 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))); 


Im Prinzip könnten wir hier aufhören, Browser zeigen SVG ohne Probleme an. Die beschriebenen Berichte wurden jedoch auch über den Telegramm-Bot abgerufen, sodass SVG in ein Format wie JPEG oder PNG konvertiert werden muss. Verwenden Sie dazu Apache Batik

In PNG konvertieren
 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; } 


Der Bericht in Form einer Karte ist fertig. Es kann auch über den Browser angezeigt und vom Telegramm-Bot angefordert werden. Meiner Meinung nach nicht schlecht.

Fazit


Zum festgelegten Zeitpunkt hatten wir Zeit und bereits im März 2017 wurden regelmäßig Leistungsindikatoren anstelle von Excel in das erstellte System eingegeben. Einerseits wurde das maximale Problem nicht gelöst, der schwierigste Teil der Berechnung der Indikatoren erfolgt manuell. Andererseits birgt die Durchführung dieser Berechnungen das Risiko einer ständigen Verfeinerung. Darüber hinaus hat selbst die erstellte einfache Oberfläche zum Sammeln von Daten eine Vielzahl von Fragen mit ständigen Änderungen, Versionskontrolle und einer Zusammenfassung der Excel-Tabelle entfernt. Eine große Anzahl von manuellen Arbeiten, Überprüfungen und Gegenprüfungen wurde entfernt.

Es ist unmöglich nicht zu sagen, dass die Oberfläche von Open Xav den Benutzern nicht allzu angenehm war. Anfangs gab es viele Fragen zu seinen Funktionen. Irgendwann beschwerten sich die Benutzer darüber, dass die Eingabe von Daten zu lange dauerte und im Allgemeinen „wir wollen, wie in Excel, nur ein Programm“. Ich musste sogar die Eingabegeschwindigkeit anhand von Daten zum Zeitpunkt der Erstellung von Datensätzen überwachen. Diese Überwachung ergab, dass Benutzer selbst in den schwerwiegendsten Fällen nicht mehr als 15 Minuten für die Eingabe aufgewendet haben, sondern in der Regel in 5-7 passen, obwohl sie Daten in 22 Filialen eingeben mussten. Solche Indikatoren scheinen durchaus akzeptabel.

Ich möchte jedoch zwei Dinge beachten:

  1. Open Xava erwies sich als gutes Werkzeug zum schnellen Erstellen einer Benutzeroberfläche. Ich würde sogar eine Prototyp-Schnittstelle sagen. Auch sein unbestrittener Vorteil ist die allgemeine Ordnung und Regelmäßigkeit. Alle Formulare in der Anwendung werden nach einheitlichen Grundsätzen erstellt, sodass der Entwickler keine Fahrräder entwickeln kann, bei denen er sie nicht benötigt, sondern der Benutzer sich mit Standardformularen befassen muss. Versuche, eine komplexere Logik zu implementieren oder die Steuerelemente für uns selbst zu ändern, führten uns jedoch zu einer Reihe von Problemen, die wir in der vorgegebenen Zeit nicht bewältigen konnten. Höchstwahrscheinlich haben wir es einfach nicht verstanden und das Ziel war es, mit minimalem Aufwand eine einfache CRUD-Schnittstelle zu erstellen. Ich komme zu dem Schluss, dass Open Xava ein interessantes Tool ist, mit dem es einfach ist, einfache Dinge zu tun. Wenn Sie jedoch etwas Kompliziertes tun müssen, würde ich lieber mehr Zeit damit verbringen, den Client-Teil mit ExtJS oder React zu erstellen, aber mehr Flexibilität zu haben .
  2. Selbst selbstbewusste Benutzer nehmen neue Schnittstellen schwer. Das ist natürlich kein Geheimnis. Meiner Meinung nach ist dies hauptsächlich auf ein mangelndes Verständnis der systemischen Natur vieler Schnittstellen zurückzuführen. Für viele hat jede Anwendung eine Reihe von Bildschirmen, von denen jeder einzigartig ist und nach ihren eigenen unbekannten Prinzipien arbeitet: Zum Beispiel gibt es ein Formular mit einer Liste von Objekten / Zeilen (Listenformular), aber für so viele Benutzer ist es keineswegs offensichtlich, dass jedes dieser Formulare in der Anwendung vorhanden sein kann Gleichmäßiges Filtern, Paginieren, Sortieren und im Allgemeinen das gleiche Verhalten, ergänzt durch spezifische Funktionen. Hinzu kommt, dass eine große Anzahl von Unternehmenssoftware ein fast spontaner Stapel von Schaltflächen und Formularen ist, der mit einer undeutlichen Dokumentation im Stil von "Klicken Sie auf diese Schaltfläche" versehen ist. Unter diesem Gesichtspunkt sorgen die von denselben Open Xav-Benutzerbenutzern erstellten Schnittstellen für mehr Ordnung im Kopf. Bei diesem Ansatz verschwinden die magischen Knöpfe zwar nirgendwo, sondern werden ordentlich in Formen angeordnet.

Wenn wir über die Vorteile für die gesamte Organisation sprechen, hat sich der Kontrollgrad nach Einführung der Anwendung erwartungsgemäß erhöht. Die Feldleiter erhielten ein Tool zur Überwachung von Indikatoren, mit dem sie die Arbeit in einem betriebsfähigeren Modus beeinflussen und Indikatoren für verschiedene Zeiträume vergleichen können, ohne mit dem Fluss der gesendeten Dateien verwechselt zu werden. Das Interesse der Führungskräfte an der ständigen und aufmerksamen Überwachung ihrer Niederlassung wurde von uns auf sehr interessante Weise entdeckt: Beim Durchsuchen der Telegramm-Bot-Protokolle stellten wir fest, dass einige Berichte um 4 oder 5 Uhr morgens eingingen. Sie sehen sofort, wer sich für Arbeit interessiert.

Das ist alles. Für das konstruktive Feedback wäre ich dankbar!

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


All Articles