Cómo automatizar la recopilación de KPI por mes y dejar a los usuarios casi satisfechos

En muchas organizaciones, la evaluación de la unidad se realiza utilizando KPI (Indicadores clave de rendimiento). En la organización donde trabajo, dicho sistema se llama "sistema de indicadores de rendimiento", y en este artículo quiero hablar sobre cómo logramos automatizar parte del trabajo con indicadores en un mes. Con todo esto, nuestros costos de mano de obra no fueron los más grandes, pero al mismo tiempo tratamos de implementar una lista de deseos de larga data. En mi historia no habrá revelaciones o tecnologías exageradas (después de todo, el desarrollo provincial es duro), pero habrá algunos bocetos sobre el tema que ayudarán a comprender cómo comenzamos, qué hicimos y qué pensamientos obtuvimos del desarrollo. Si aún no te has aburrido, te pido un gato.

Antecedentes


En 2006, introdujimos un sistema de indicadores de desempeño: se desarrollaron quince criterios de evaluación, una metodología para calcularlos y se estableció una frecuencia trimestral para calcular estos indicadores. La evaluación se realizó para veintidós sucursales de la organización ubicadas en todas las áreas de nuestra región. Y para que los indicadores se alcanzaran con gran entusiasmo, se les asignó un bono: cuanto mayor es la suma de los indicadores, mayor es el lugar en la calificación, mayor es el lugar en la calificación, mayor es la prima, y ​​así sucesivamente cada trimestre y cada año.

Con el tiempo, la composición de los criterios cambió; cada trimestre, se agregaron nuevos o se excluyeron los antiguos. En el pico, alrededor de 2016, el número de indicadores superó los cuarenta, y ahora solo hay catorce.

Sin embargo, todo este tiempo el proceso de cálculo fue el mismo. La unidad responsable de la organización matriz calcula cada criterio de acuerdo con un algoritmo aprobado específicamente para este criterio. El cálculo puede llevarse a cabo tanto por una fórmula simple como por una serie de complejas, puede requerir la recopilación de datos de varios sistemas, mientras que es muy probable que el algoritmo cambie con bastante frecuencia. Y entonces todo ya es mucho más simple: el porcentaje del indicador calculado se multiplica por su coeficiente, por lo tanto, se obtiene una puntuación por el criterio, luego se clasifican los puntos y cada unidad se lleva a cabo de acuerdo con la puntuación. Lo mismo se hace con la suma de puntos para calcular el lugar final.

Desde el principio, Excel se utilizó para tener en cuenta y calcular todo lo descrito anteriormente. Durante muchos años, el conjunto final de criterios y el cálculo adicional de puntos y lugares se llevaron a cabo en un buen plato, parte del cual se muestra en la figura.



Solo noto que en esta bonita tableta, la mayoría de los altavoces están simplemente ocultos, pero en realidad en el cuarto trimestre de 2016 se veía así



De acuerdo, tal cantidad de indicadores y tal volumen de la tabla no se suman a su simplicidad. Además, cada uno de los criterios se calculó por separado, y las niñas del departamento general hicieron esta tabla resumen con sus manos. Teniendo en cuenta que no solo se modificó la composición de los criterios, sino que también se pudieron volver a hacer los cálculos varias veces (por muchas razones), los últimos días de cada trimestre, por decirlo suavemente, no fueron felices. Tuve que cambiar constantemente los datos en la tabla, verificarlos y verificarlos dos veces: así, se olvida de agregar una nueva columna a la suma de puntos o no actualiza el coeficiente, y debido a esto, alguien tiene un lugar más bajo, y con ello, la bonificación es menor. Por lo tanto, después de la reducción heroica de la tabla, comenzó una verificación no menos heroica, todos los que consideraron cada criterio, los gerentes e incluso un especialista en TI asignado por la organización verificaron y aceptaron. Teniendo en cuenta que todo el trabajo se realizó manualmente y que el cálculo de los indicadores para la tabla dinámica se envió por correo, a menudo hubo situaciones en las que la tabla dinámica contenía errores en las fórmulas y los datos de versiones irrelevantes. El capitán de evidencia informa que después de que se detectó cada error, se reinició el proceso de verificación.
Cuando se calculan todos los indicadores, la tableta se aprueba y se envía a las sucursales, allí, en las sucursales, se verificó dos veces: ¿qué pasa si el insidioso Excel pensó que algo estaba mal? Creo que es obvio que compilar un informe anual sobre los criterios o analizar datos históricos resultó ser una búsqueda no menos emocionante.

Sin embargo, desde un punto de vista gerencial, dicho sistema es bastante efectivo y permite, si es necesario, ajustar áreas de trabajo caídas, monitorear y tener una idea de lo que está sucediendo en cada una de las ramas en un área de trabajo en particular. Además, la ubicación de la sucursal, calculada según los criterios, era un indicador integral.

El sistema debe cambiar


La práctica ha demostrado la necesidad y la importancia de un sistema de evaluación. A principios de 2017, quedó claro que calcular los criterios una vez por trimestre permitía evaluar el trabajo realizado, pero monitorearlo permitió que fuera débil. Demasiado tiempo. En este sentido, se decidió que el cálculo de los indicadores se realizará una vez cada dos semanas, y los resultados se resumirán trimestralmente. El aumento en la frecuencia de cálculo realmente permitió a los gerentes de sucursales responder rápidamente a los cambios y aumentar el control sobre los procesos dentro de sus unidades.

Pero solo en la organización matriz, pocos estaban satisfechos con la posibilidad de llevar a cabo el proceso de recopilación de datos descrito anteriormente, no cada trimestre, sino cada dos semanas. Como no es difícil de adivinar, decidieron automatizar el proceso. Los plazos resultaron ser ajustados: desde el momento de la decisión de cambiar a una nueva frecuencia de cálculo, hasta la transición real, solo debería haber pasado un mes. Para este período, era muy deseable encontrar algo mejor que un montón de Excel para el código y el cálculo, y enviarlo por correo para su recopilación y notificación.

Al principio hubo una discusión bastante acalorada sobre lo que realmente, realmente tenía que hacerse automáticamente y el cálculo de los indicadores en sí, teniendo en cuenta todas sus fórmulas y fuentes de datos. Pero dados los plazos ajustados, la complejidad de dicha funcionalidad y la necesidad de su mantenimiento constante en el estado actual lograron cumplir con los siguientes requisitos para el sistema:

  • Se debe mantener un manual de referencia de criterios, preservando la historia de su cambio;
  • Debería ser posible ingresar y almacenar los indicadores calculados, así como su conversión a puntos como en la misma placa;
  • Con base en los valores de los indicadores, se debe generar un informe que sea accesible para todas las partes interesadas;
  • Naturalmente, todo esto debe proporcionarse con una interfaz web.

La funcionalidad es muy pequeña, pero no mucho tiempo.

Inicio del desarrollo


Siempre me atrajeron los medios de desarrollo rápido y la generación automática de interfaces. En los casos en que es necesario implementar la funcionalidad CRUD, la idea de cuándo la interfaz y parte de la lógica de negocios se proporcionará de manera inmediata, por así decirlo, parece muy tentadora. Por supuesto, la interfaz será lógica sin pretensiones y torpe, pero para muchas tareas esto es suficiente.

Impulsado por estas ideas, decidí probar algo así. Hay Spring Roo, Cuba y otras herramientas interesantes, pero la elección recayó en OpenXava. En primer lugar, una vez que ya hice una aplicación muy simple y estaba satisfecho, y en segundo lugar, este marco en ese momento se ajustaba con éxito a nuestra pila tecnológica. Además, es muy agradable tener un breve tutorial en ruso.

Una breve descripción de las características y capacidades de OpenXava se puede encontrar aquí . OpenXava es un marco que implementa la construcción automática de una interfaz web integrada con una base de datos basada en JPA, y utiliza anotaciones para describir las reglas de visualización. La aplicación se basa en componentes empresariales: clases de Java que contienen la información necesaria para crear aplicaciones. Dicha información incluye estructura de datos, validadores, representaciones válidas, mapeo a tablas de bases de datos. Las operaciones en componentes comerciales se llevan a cabo a través de controladores que pueden CRUDAR desde un cuadro, buscar, exportar a PDF, etc. La aplicación OpenXava presenta un conjunto de módulos. Un módulo asocia un componente comercial con uno o más controladores. Las representaciones definidas para cada componente empresarial se utilizan para mostrar la interfaz. Nada inusual, MVC con un poco de su propia atmósfera.

Almacenamiento de datos


En la mayoría de las aplicaciones, utilizamos el DBMS de IBM DB2. Se creó una pequeña base de datos que almacena los libros de referencia de grupos de criterios y criterios por los cuales se evalúan las ramas, el directorio de ramas y una placa en la que se ingresan los valores calculados de los criterios. Para cada criterio, en un determinado momento, se asigna un coeficiente que se utilizará en la puntuación. Cada rama, para cada criterio, recibe una evaluación también para una fecha determinada. Los datos sobre los valores de los criterios se almacenan históricamente, es decir, los datos relevantes para cualquier fecha serán aquellos que se ingresaron en la fecha más cercana en el pasado. Este enfoque, inspirado en los registros de información de 1C: Enterprise, me parece lo suficientemente conveniente: hay un historial y los problemas de edición / eliminación no son particularmente elevados.

Estructura de la base de datos
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 ); 


Para obtener el codiciado informe sobre el rendimiento de las ramas de la base de datos, se crearon funciones almacenadas, cuyos resultados ya están asignados a la clase Java. Las funciones se parecen a esto.
Primero obtenemos todos los criterios válidos para la fecha, así como los coeficientes de criterio relevantes para esa fecha.
 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 ; 


Ahora recibiremos en la misma fecha los valores de todos los criterios para todas las sucursales
 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 ; 


En la consulta final, numeramos los valores de los indicadores, los clasificamos y encontramos el mínimo y el máximo, esto será necesario para calcular los lugares más adelante
 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 


La transferencia de parte de la lógica de negocios al DBMS está bastante justificada, especialmente cuando se trata de preparar datos para diferentes informes. Resulta que las operaciones se pueden escribir en una forma más concisa y natural; en Java, tales manipulaciones con los datos requerirían una mayor cantidad de código y algunos esfuerzos para estructurarlo. Aunque las operaciones relativamente complejas o no triviales son aún más fáciles de programar en Java. Por lo tanto, en nuestra aplicación, con respecto al muestreo de datos, se utiliza un enfoque donde la conexión de conjuntos de datos, condiciones de recorte y algunas operaciones que pueden realizarse mediante funciones de ventana se realizan en funciones y procedimientos almacenados, y se implementa una lógica más compleja en la aplicación.

App


Como dije, OpenXava se utilizó para implementar la aplicación. Para obtener una interfaz típica y CRUD fuera de la caja, debe realizar algunas acciones.
Para empezar, en web.xml debe describir el filtro y el servlet del complemento que navega por la aplicación:

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> 


A continuación, en el archivo controllers.xml, definimos los controladores utilizados en la aplicación. En nuestro caso, lo más simple es suficiente:

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


El controlador anterior combina las funciones de los controladores incluidos en OpenXava de forma predeterminada, cuyas funciones son fáciles de adivinar a partir de los nombres.

Y finalmente, en el archivo application.xml conectaremos el controlador creado y el modelo. Algo como esto:

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


Como se mencionó anteriormente, la aplicación se basa en los componentes comerciales que conforman el modelo de la aplicación. Por ejemplo, considere el componente RegValueCriteria asociado con el controlador en application.xml. Este componente describe el valor del criterio para la rama (por brevedad, solo queda la descripción de los campos de clase, y omitiré métodos como getters y setters):

Clase de componentes
 @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; } 


Además de las anotaciones JPA habituales. También puede notar las anotaciones de OpenXava. Deben considerarse con más detalle.

La @View permite controlar el formato de presentación de los campos de clase, y con la ayuda de una sintaxis especial en forma de corchetes, los campos se pueden combinar en grupos y organizarse horizontal y verticalmente usando símbolos , y ; . Si es necesario especificar varias asignaciones para un solo componente, las anotaciones @View se agrupan utilizando @View anotaciones @View . En nuestro ejemplo, las propiedades se organizaron de la siguiente manera:

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

Y se ve así:



Sin pretensiones, pero con un mínimo de esfuerzo. Sin embargo, hay formas de "revivir" un poco la forma.
Para que se @DefaultValueCalculator la fecha de registro al crear el componente, se @DefaultValueCalculator anotación @DefaultValueCalculator , que llama a un objeto de calculadora especial. Aquí, se usa una calculadora de OpenXava, pero también puede hacer una personalizada. La anotación @Stereotype se usa para mostrar la fecha usando el control apropiado.

Para configurar listas desplegables que contienen objetos relacionados, se @DescriptionsList anotación @DescriptionsList , en la que puede especificar qué propiedad se mostrará en la lista.

Usando anotaciones, puede implementar alguna lógica de negocios del formulario en sí. Por ejemplo, para que cuando el porcentaje cambie, el valor se calcule automáticamente teniendo en cuenta el coeficiente del criterio, puede aplicar la anotación @OnChange para el campo BigDecimal val . Para que la anotación @OnChange funcione, debe apuntar a la clase que implementa la interfaz OnChangePropertyBaseAction . Se debe implementar un único método execute() en la clase, en la que los datos de entrada se toman de la vista, se realiza el cálculo y el valor calculado se vuelve a escribir en la vista:

Clase secundaria 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); } } } } } 


Para una presentación tabular de datos, se @Tab anotación @Tab , que le permite enumerar las propiedades de los objetos que se mostrarán en una representación tabular. En nuestro ejemplo, la anotación se enmarca como sigue:

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

Se verá de la siguiente manera



Complacido con la disponibilidad de filtros, paginación y exportación fuera de la caja, sin embargo, muchos detalles requieren refinamiento con un archivo.

El trabajo con otros componentes se construye de manera similar. El uso de OpenXava ha reducido drásticamente los costos laborales para implementar las funciones CRUD y la mayor parte de la interfaz de usuario. Usar acciones de controladores predefinidos y anotaciones para crear formularios ahorra mucho tiempo si no encuentra fallas en los detalles e intenta implementar algo más complicado que el formulario de entrada con varios eventos. Aunque puede ser el caso en la experiencia.

De qué se trataba todo


¿Recuerdas para qué servía la aplicación? Sí, para que la placa con indicadores no se reduzca heroicamente en Excel, sino que se cree automáticamente en función de los datos ingresados. En la ventana del navegador, la tabla dinámica comenzó a verse así:




No daré detalles de la implementación, ya que no todo es muy bueno y la combinación de JSP con el HTML generado al solicitar datos no es algo para compartir con el público en general. Al mismo tiempo, el muestreo de datos en sí se demostró anteriormente.

Sin embargo, quiero detenerme en un detalle interesante. Cuando se recopilaron los requisitos para la aplicación, la gerencia realmente quería que, además del informe resumido, los valores de un indicador individual pudieran mostrarse en forma de un mapa de la región, dividido en regiones con una indicación de la ubicación y el puntaje de la rama correspondiente. Quién reconoció la región en el mapa - bien hecho =)



Por un lado, el requisito era opcional, pero por otro lado, la imagen prometía ser visual y, desde el punto de vista de la implementación, era interesante intentarlo. Después de pensarlo un poco, surgió la idea de encontrar la imagen de la región en formato SVG y crear una plantilla XSLT.

La plantilla resultante se llena fácilmente con datos y luego se convierte a PNG.
Primero, usando la consulta descrita anteriormente, los datos se muestrean, los datos recibidos se convierten en un objeto de esta clase:

Clases para enviar datos a un mapa
 @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; } } } 


Luego, convierta el objeto a XML;

Conversión
 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(); } 


Ahora, tomemos el XML resultante, la plantilla XSLT preparada, aplique la conversión y obtenga svg en la salida:

Obtener 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 principio, podríamos detenernos aquí, los navegadores muestran SVG sin ningún problema. Pero los informes descritos también se obtuvieron a través del bot Telegram, por lo que SVG debe convertirse a algún formato como JPEG o PNG. Para hacer esto, use Apache Batik

Convertir a 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; } 


El informe en forma de mapa está listo. También se puede ver a través del navegador y solicitarlo al bot de telegramas. En mi opinión, no está mal.

Conclusión


En el momento señalado, teníamos tiempo y ya en marzo de 2017, los indicadores de rendimiento en lugar de Excel comenzaron a ingresar regularmente en el sistema creado. Por un lado, el problema máximo no se ha resuelto; la parte más difícil de calcular los indicadores se hace manualmente. Pero, por otro lado, la implementación de estos cálculos conllevaba el riesgo de un refinamiento constante. Además, incluso la interfaz simple creada para recopilar datos eliminó una gran cantidad de preguntas con cambios constantes, control de versiones y un resumen de la hoja de cálculo de Excel. Se ha eliminado una gran cantidad de trabajo manual, controles y verificaciones cruzadas.

Es imposible no decir que la interfaz en Open Xav no era demasiado agradable para los usuarios. Al principio, había muchas preguntas sobre sus características. En algún momento, los usuarios comenzaron a quejarse de que tomó demasiado tiempo ingresar datos y, en general, "queremos, como en Excel, solo un programa". Incluso tuve que controlar la velocidad de entrada en función de los datos en el momento de la creación de los registros. Este monitoreo mostró que incluso en los casos más severos, los usuarios no dedicaron más de 15 minutos a la entrada, pero generalmente se ajustaban en 5-7, a pesar del hecho de que necesitaban ingresar datos en 22 sucursales. Tales indicadores parecen bastante aceptables.

Sin embargo, quiero señalar dos cosas:

  1. Open Xava demostró ser una buena herramienta para crear rápidamente una interfaz. Incluso diría una interfaz prototipo. También su ventaja indudable es el orden general y la regularidad. Todos los formularios de la aplicación se crean de acuerdo con principios uniformes, lo que permite al desarrollador no proponer bicicletas donde no las necesita, sino que el usuario debe manejar conjuntos de formularios estándar. Sin embargo, los intentos de implementar una lógica más compleja o cambiar los controles por nosotros mismos nos llevaron a una serie de problemas que no pudimos resolver en el tiempo asignado. Lo más probable es que simplemente no lo entendiéramos, y el objetivo era crear una interfaz CRUD simple con un mínimo de esfuerzo. Por mi parte, concluyo que Open Xava es una herramienta interesante en la que es fácil hacer cosas simples, pero si necesita hacer algo complicado, preferiría pasar más tiempo creando la parte del cliente usando ExtJS o React, pero tengo más flexibilidad .
  2. Incluso los usuarios seguros confían mucho en las nuevas interfaces. Por supuesto, esto no es ningún secreto. En mi opinión, esto se debe principalmente a la falta de comprensión de la naturaleza sistémica de muchas interfaces. Para muchos, cualquier aplicación tiene un conjunto de pantallas, cada una de las cuales es única y funciona de acuerdo con sus propios principios desconocidos: por ejemplo, hay un formulario con una lista de objetos / líneas (formulario de lista), pero para tantos usuarios no es del todo obvio que cada uno de esos formularios en la aplicación puede tener filtrado uniforme, paginación, funciones de clasificación y, en general, el mismo comportamiento, complementado por funciones específicas. Esto se ve agravado por el hecho de que una gran cantidad de software corporativo es una pila casi espontánea de botones y formularios, aderezada con documentación poco clara al estilo de "Haga clic en este botón". Desde este punto de vista, las interfaces creadas por los mismos desarrolladores de usuarios de disciplina Open Xav crean más orden en la cabeza. Es cierto que con este enfoque, los botones mágicos no desaparecen en ningún lado, sino que se colocan cuidadosamente en formas.

Si hablamos de los beneficios para la organización en su conjunto, luego de la presentación de la aplicación, como era de esperar, el grado de control aumentó. Los líderes de campo recibieron una herramienta para monitorear indicadores, con la cual pueden influir en el trabajo en un modo más operativo, comparar indicadores para diferentes períodos, sin confundirse con el flujo de archivos enviados. Descubrimos el interés de los líderes en el monitoreo constante y vigilante de su sucursal de una manera muy interesante: al mirar a través de los registros de bot de Telegram, notamos que se recibían algunos informes a las 4 o 5 de la mañana. Puede ver de inmediato quién está interesado en el trabajo.

Eso es todo. Estaría agradecido por los comentarios constructivos!

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


All Articles