Como automatizar a coleta de KPI por mês e deixar os usuários quase satisfeitos

Em muitas organizações, a avaliação da unidade é realizada usando KPI (Key Performance Indicators). Na organização em que trabalho, esse sistema é chamado de "sistema de indicadores de desempenho" e, neste artigo, quero falar sobre como conseguimos automatizar parte do trabalho com indicadores dentro de um mês. Com tudo isso, nossos custos de mão-de-obra não foram os maiores, mas, ao mesmo tempo, tentamos implementar alguma lista de desejos de longa data. Na minha história, não haverá tecnologias ou revelações exageradas (afinal, o desenvolvimento provincial é duro), mas haverá alguns esboços sobre o tópico que ajudarão a entender como começamos, o que fizemos e que pensamentos tivemos do desenvolvimento. Se você ainda não se cansou, peço um gato.

Antecedentes


Em 2006, introduzimos um sistema de indicadores de desempenho: quinze critérios de avaliação foram desenvolvidos, uma metodologia para calculá-los e uma frequência trimestral para o cálculo desses indicadores. A avaliação foi realizada em vinte e duas filiais da organização localizadas em todas as áreas da nossa região. E, para que os indicadores sejam alcançados com grande entusiasmo, um bônus foi atrelado a eles - quanto maior a soma dos indicadores, maior o lugar na classificação, quanto maior o lugar na classificação, maior o prêmio e assim por diante a cada trimestre e cada ano.

Com o tempo, a composição dos critérios foi alterada; a cada trimestre foram adicionados novos ou excluídos os antigos. No pico, em cerca de 2016, o número de indicadores ultrapassou quarenta, e agora existem apenas quatorze.

No entanto, durante todo esse tempo, o processo de cálculo foi o mesmo. Cada critério é calculado pela unidade responsável da organização mãe, de acordo com um algoritmo aprovado especificamente para esse critério. O cálculo pode ser realizado tanto por uma fórmula simples quanto por várias complexas, pode exigir a coleta de dados de vários sistemas, embora seja muito provável que o algoritmo mude com bastante frequência. E então tudo já é muito mais simples: o indicador percentual calculado é multiplicado pelo seu coeficiente, assim, uma pontuação é obtida pelo critério, os pontos são classificados e cada unidade ocorre de acordo com a pontuação. O mesmo é feito com a soma dos pontos para o cálculo do local final.

Desde o início, o Excel foi usado para levar em conta e calcular tudo descrito acima. Por muitos anos, o conjunto final de critérios e o cálculo adicional de pontos e locais foram realizados em uma bela placa, parte da qual é mostrada na figura.



Eu só observo que neste belo tablet, a maioria dos alto-falantes está simplesmente oculta, mas, na realidade, no 4º trimestre de 2016, ficou assim



Concordo que esse número de indicadores e esse volume da tabela não aumentam sua simplicidade. Além disso, cada um dos critérios foi calculado separadamente, e as meninas do departamento geral elaboraram esta tabela de resumo com as mãos. Considerando que não apenas a composição dos critérios mudou, mas também os cálculos podem ser refeitos várias vezes (por várias razões), os últimos dias de cada trimestre, para dizer o mínimo, não foram felizes. Eu tive que mudar constantemente os dados da tabela, verificar e checar novamente: assim, você esquece de adicionar uma nova coluna à soma dos pontos ou não atualiza o coeficiente, e para algumas pessoas o local fica mais baixo e, com isso, o bônus é menor. Portanto, após a heróica redução da tabela, não começou uma verificação menos heróica, todos aqueles que consideraram cada critério, gerentes e até mesmo um especialista em TI designado pela organização verificaram e concordaram. Considerando que todo o trabalho foi feito manualmente e o cálculo dos indicadores para a tabela dinâmica foi enviado para o correio, muitas vezes houve situações em que a tabela dinâmica continha erros nas fórmulas e nos dados de versões irrelevantes. O capitão de evidências relata que, após a detecção de cada erro, o processo de verificação foi reiniciado.
Quando todos os indicadores são calculados, o tablet é aprovado e enviado para as filiais, nas filiais foi verificado duas vezes: e se o insidioso Excel pensasse que algo estava errado? Penso que é óbvio que compilar um relatório anual sobre os critérios ou analisar dados históricos acabou não sendo uma missão menos emocionante.

No entanto, do ponto de vista gerencial, esse sistema é bastante eficaz e permite, se necessário, restringir áreas de trabalho frouxas, monitorar e ter uma idéia do que está acontecendo em cada uma das filiais em uma área de trabalho específica. Além disso, a localização da agência, calculada de acordo com os critérios, era um indicador integral.

O sistema deve mudar


A prática demonstrou a necessidade e a importância de um sistema de avaliação. No início de 2017, ficou claro que o cálculo dos critérios uma vez por trimestre tornava possível avaliar o trabalho realizado, mas o monitoramento permitia que ele fosse fraco. Período de tempo muito longo. Nesse sentido, foi decidido que o cálculo dos indicadores será realizado a cada duas semanas e os resultados serão resumidos trimestralmente. O aumento na frequência de cálculo realmente permitiu que os gerentes das agências respondessem rapidamente às mudanças e aumentassem o controle sobre os processos em suas unidades.

Mas apenas na organização mãe, poucos ficaram satisfeitos com a perspectiva de realizar o processo de coleta de dados descrito acima, não a cada trimestre, mas a cada duas semanas. Como não é difícil adivinhar, eles decidiram automatizar o processo. Os prazos acabaram sendo apertados: desde o momento da decisão de mudar para uma nova frequência de cálculo, até a transição real, apenas um mês deveria ter passado. Durante esse período, era muito desejável criar algo melhor do que um monte de Excel para código e cálculo e correio para coleta e notificação.

No início, houve muita discussão acalorada sobre o que realmente precisava ser feito automaticamente e o cálculo dos próprios indicadores, levando em consideração todas as suas fórmulas e fontes de dados. Porém, dados os prazos apertados, a complexidade de tal funcionalidade e a necessidade de sua manutenção constante no estado atual conseguiram atingir os seguintes requisitos para o sistema:

  • Um manual de referência de critérios deve ser mantido, preservando o histórico de suas alterações;
  • Deverá ser possível inserir e armazenar os indicadores calculados, bem como sua conversão em pontos, como na mesma placa;
  • Com base nos valores dos indicadores, deve ser gerado um relatório acessível a todas as partes interessadas;
  • Naturalmente, tudo isso deve ser fornecido com uma interface da web.

A funcionalidade é muito pequena, mas não há muito tempo.

Início do desenvolvimento


Eu sempre fui atraído pelos meios de desenvolvimento rápido e geração automática de interfaces. Nos casos em que é necessário implementar a funcionalidade CRUD, a idéia de quando a interface e parte da lógica de negócios será fornecida imediatamente, por assim dizer, parece muito tentadora. Obviamente, a interface será lógica despretensiosa e desajeitada, mas para muitas tarefas isso é suficiente.

Movido por essas idéias, decidi tentar algo assim. Existem Spring Roo, Cuba e outras ferramentas interessantes, mas a escolha caiu no OpenXava. Primeiro, uma vez que eu já fiz uma aplicação muito simples e fiquei satisfeito; em segundo lugar, essa estrutura na época se encaixava com sucesso em nossa pilha tecnológica. Além disso, é muito agradável ter um breve tutorial em russo.

Uma breve descrição dos recursos e capacidades do OpenXava pode ser encontrada aqui . O OpenXava é uma estrutura que implementa a construção automática de uma interface da web integrada a um banco de dados baseado em JPA e usa anotações para descrever as regras de visualização. O aplicativo é baseado em componentes de negócios - classes Java que contêm as informações necessárias para criar aplicativos. Essas informações incluem estrutura de dados, validadores, representações válidas, mapeamento para tabelas de banco de dados. As operações nos componentes de negócios são realizadas por meio de controladores que podem CRUD a partir de uma caixa, pesquisar, exportar para PDF, etc. O aplicativo OpenXava apresenta um conjunto de módulos. Um módulo associa um componente de negócios a um ou mais controladores. Representações definidas para cada componente de negócios são usadas para exibir a interface. Nada incomum, MVC com um pouco de sua própria atmosfera.

Armazenamento de dados


Na maioria dos aplicativos, usamos o IBM DB2 DBMS. Foi criado um pequeno banco de dados que armazena os livros de referência de grupos de critérios e critérios pelos quais as ramificações são avaliadas, o diretório de ramificações e uma placa na qual os valores calculados dos critérios são inseridos. Para cada critério, em um determinado momento, é atribuído um coeficiente que será usado na pontuação. Cada filial, para cada critério, recebe uma avaliação também por uma determinada data. Os dados sobre os valores dos critérios são armazenados historicamente, ou seja, os dados relevantes para qualquer data serão aqueles que foram inseridos na data mais próxima no passado. Essa abordagem, inspirada nos registros de informações de 1C: Enterprise, parece conveniente o suficiente para mim: há um histórico e os problemas de edição / exclusão não são particularmente altos.

Estrutura de banco de dados
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 obter o cobiçado relatório sobre o desempenho das ramificações do banco de dados, foram criadas funções armazenadas, cujos resultados já estão mapeados para a classe Java. As funções se parecem com isso.
Primeiro, obtemos todos os critérios válidos para a data, bem como os coeficientes relevantes para essa data
 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 ; 


Agora receberemos na mesma data os valores de todos os critérios para todas as filiais
 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 ; 


Na consulta final, numeramos os valores dos indicadores, os classificamos e encontramos o mínimo e o máximo, isso será necessário para calcular os locais posteriormente
 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 


A transferência de parte da lógica de negócios para o DBMS é bastante justificada, principalmente quando se trata de preparar dados para diferentes relatórios. Acontece que as operações podem ser escritas de uma forma mais concisa e natural; em Java, essas manipulações com os dados exigiriam uma quantidade maior de código e alguns esforços para estruturá-lo. Embora operações relativamente complexas ou não triviais ainda sejam mais fáceis de programar em Java. Portanto, em nossa aplicação, com relação à amostragem de dados, é utilizada uma abordagem em que a conexão de conjuntos de dados, condições de recorte e algumas operações que podem ser executadas por funções de janela são executadas em funções e procedimentos armazenados, e uma lógica mais complexa é implementada no aplicativo.

App


Como eu disse, o OpenXava foi usado para implementar o aplicativo. Para obter uma interface típica e CRUD fora da caixa, você precisa executar algumas ações.
Para começar, no web.xml, você precisa descrever o filtro e o servlet do complemento que navega no aplicativo:

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> 


Em seguida, no arquivo controllers.xml, definimos os controladores usados ​​no aplicativo. No nosso caso, o mais simples é suficiente:

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


O controlador acima combina as funções dos controladores incluídos no OpenXava por padrão, cujas funções são fáceis de adivinhar pelos nomes.

E, finalmente, no arquivo application.xml, conectaremos o controlador criado e o modelo. Algo assim:

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


Como mencionado acima, o aplicativo é baseado nos componentes de negócios que compõem o modelo de aplicativo. Por exemplo, considere o componente RegValueCriteria associado ao controlador em application.xml. Este componente descreve o valor do critério para a ramificação (por questões de brevidade, apenas a descrição dos campos da classe é deixada, e eu omito métodos como getters e setters):

Classe de componente
 @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; } 


Além das anotações usuais da JPA. Você também pode observar anotações do OpenXava. Eles devem ser considerados com mais detalhes.

A @View permite controlar o formato de apresentação dos campos de classe e, com a ajuda de sintaxe especial na forma de colchetes, os campos podem ser combinados em grupos e organizados horizontal e verticalmente usando símbolos ; . Se for necessário especificar vários mapeamentos para um único componente, @View anotações do @View serão agrupadas usando @View anotações do @View . No nosso exemplo, as propriedades foram organizadas da seguinte maneira:

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

E fica assim:



Despretensioso, mas com um mínimo de esforço. No entanto, existem maneiras de "reviver" o formulário um pouco.
Para que a data de registro seja preenchida ao criar o componente, é @DefaultValueCalculator anotação @DefaultValueCalculator , que chama um objeto especial da calculadora. Uma calculadora do próprio OpenXava é usada aqui, mas você também pode criar uma personalizada. A anotação @Stereotype é usada para exibir a data usando o controle apropriado.

Para configurar listas suspensas contendo objetos relacionados, é @DescriptionsList anotação @DescriptionsList , na qual é possível especificar qual propriedade será exibida na lista.

Usando anotações, você pode implementar alguma lógica comercial do próprio formulário. Por exemplo, para que, quando a porcentagem for alterada, o valor seja calculado automaticamente levando em consideração o coeficiente do critério, você possa aplicar a anotação @OnChange ao campo BigDecimal val . Para que a anotação @OnChange funcione, é necessário apontar para a classe que implementa a interface OnChangePropertyBaseAction . Um único método execute() deve ser implementado na classe, na qual os dados de entrada são retirados da visualização, o cálculo é realizado e o valor calculado é gravado de volta na visualização:

Classe filho 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 uma apresentação tabular dos dados, é @Tab anotação @Tab , que permite listar as propriedades dos objetos que serão exibidos em uma representação tabular. No nosso exemplo, a anotação foi organizada da seguinte maneira:

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

Ele terá a seguinte aparência



Satisfeito com a disponibilidade de filtros, paginação e exportação pronta para uso, no entanto, muitos detalhes exigem refinamento com um arquivo.

O trabalho com outros componentes é construído de forma semelhante. O uso do OpenXava reduziu drasticamente os custos de mão-de-obra para implementar funções CRUD e a maior parte da interface do usuário. O uso de ações de controladores e anotações predefinidos para criar formulários economiza bastante tempo se você não discutir os detalhes e tentar implementar algo mais complicado que o formulário de entrada com vários eventos. Embora possa ser o caso na experiência.

Sobre o que era


Lembre-se do que o aplicativo estava propondo? Sim, para que a placa com indicadores não seja heroicamente reduzida no Excel, mas seja criada automaticamente com base nos dados inseridos. Na janela do navegador, a tabela dinâmica começou a ficar assim:




Não darei detalhes da implementação, pois tudo não é muito bom com ela e a mistura de JSP com o HTML gerado ao solicitar dados não é algo para compartilhar com o público em geral. Ao mesmo tempo, a própria amostragem de dados foi demonstrada acima.

No entanto, quero me debruçar sobre um detalhe interessante. Quando os requisitos para a aplicação foram coletados, a gerência realmente queria que, além do relatório de resumo, os valores de um indicador individual pudessem ser exibidos na forma de um mapa da região, dividido em regiões com uma indicação da localização e pontuação da agência correspondente. Quem reconheceu a região no mapa - muito bem =)



Por um lado, o requisito era opcional, mas, por outro lado, a imagem prometia ser visual e, do ponto de vista da implementação, era interessante tentar. Depois de pensar um pouco, surgiu a idéia de encontrar a imagem da região no formato SVG e criar um modelo XSLT.

O modelo resultante é facilmente preenchido com dados e depois é convertido em PNG.
Primeiro, usando a consulta descrita acima, os dados são amostrados, os dados recebidos são convertidos em um objeto desta classe:

Classes para enviar dados para um 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; } } } 


Em seguida, converta o objeto em XML;

Conversão
 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(); } 


Agora, vamos pegar o XML resultante, o modelo XSLT preparado, aplicar a conversão e obter svg na saída:

Obter 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))); 


Em princípio, poderíamos parar por aqui, os navegadores exibem SVG sem problemas. Mas os relatórios descritos também foram obtidos através do bot Telegram, portanto, o SVG deve ser convertido para algum formato, como JPEG ou PNG. Para fazer isso, use o Apache Batik

Converter em 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; } 


O relatório na forma de um mapa está pronto. Também pode ser visualizado através do navegador e solicitado pelo bot de telegrama. Na minha opinião, não é ruim.

Conclusão


Na hora marcada, tínhamos tempo e já em março de 2017, os indicadores de desempenho, em vez do Excel, começaram a ser inseridos regularmente no sistema criado. Por um lado, o problema máximo não foi resolvido; a parte mais difícil do cálculo dos indicadores é feita manualmente. Por outro lado, a implementação desses cálculos trazia riscos de melhoria constante. Além disso, mesmo a interface simples criada para coletar dados removeu um grande número de perguntas com alterações constantes, controle de versão e um resumo da planilha do Excel. Um grande número de trabalhos manuais, verificações e verificações cruzadas foram removidos.

É impossível não dizer que a interface no Open Xav não era muito agradável para os usuários. No início, havia muitas perguntas sobre seus recursos. Em algum momento, os usuários começaram a reclamar que demorou muito tempo para inserir dados e, em geral, "queremos, como no Excel, apenas um programa". Eu até tive que monitorar a velocidade de entrada com base nos dados no momento da criação dos registros. Esse monitoramento mostrou que, mesmo nos casos mais graves, os usuários não passavam mais de 15 minutos em entrada, mas geralmente cabiam em 5-7, apesar de precisarem inserir dados em 22 filiais. Tais indicadores parecem bastante aceitáveis.

No entanto, quero observar duas coisas:

  1. O Open Xava provou ser uma boa ferramenta para criar rapidamente uma interface. Eu diria até uma interface protótipo. Também sua vantagem indubitável é a regularidade e regularidade geral. Todos os formulários no aplicativo são criados de acordo com princípios uniformes, o que permite ao desenvolvedor não criar bicicletas onde ele não precisa, mas o usuário lidar com conjuntos de formulários padrão. No entanto, as tentativas de implementar uma lógica mais complexa ou alterar os controles por nós mesmos nos levaram a uma série de problemas com os quais não conseguimos lidar no tempo previsto. Provavelmente, nós simplesmente não entendemos, e o objetivo era criar uma interface CRUD simples com um mínimo de esforço. Por mim, concluo que o Open Xava é uma ferramenta interessante na qual é fácil fazer coisas simples, mas se você precisar fazer algo complicado, prefiro gastar mais tempo criando a parte do cliente usando ExtJS ou React, mas com mais flexibilidade .
  2. Até usuários confiáveis ​​confiam nas novas interfaces. Claro que isso não é segredo. Na minha opinião, isso se deve principalmente à falta de entendimento da natureza sistêmica de muitas interfaces. Para muitos, qualquer aplicativo possui um conjunto de telas, cada uma única e funciona de acordo com seus próprios princípios desconhecidos: por exemplo, existe um formulário com uma lista de objetos / linhas (formulário de lista), mas para muitos usuários não é de todo óbvio que cada um desses formulários no aplicativo possa ter funções uniformes de filtragem, paginação, classificação e geralmente o mesmo comportamento, complementado por funções específicas. Isso é agravado pelo fato de que um grande número de software corporativo é uma pilha quase espontânea de botões e formulários, temperada com documentação indistinta no estilo "Clique neste botão". Desse ponto de vista, as interfaces criadas pelos mesmos desenvolvedores de usuários da disciplina Open Xav criam mais ordem na cabeça. É verdade que, com essa abordagem, os botões mágicos não desaparecem em lugar algum, mas se tornam organizados de maneira organizada.

Se falamos sobre os benefícios para a organização como um todo, depois que o aplicativo foi introduzido, como esperado, o grau de controle aumentou. Os líderes de campo receberam uma ferramenta para monitorar indicadores, com os quais podem influenciar o trabalho de forma mais operacional, comparar indicadores por períodos diferentes, sem se confundir com o fluxo de arquivos enviados. O interesse dos líderes no monitoramento constante e vigilante de seu ramo foi descoberto por nós de uma maneira muito interessante: ao examinar os registros de bots do Telegram, percebemos que alguns relatórios foram recebidos às 4 ou 5 da manhã. Você pode ver imediatamente quem está interessado no trabalho.

Só isso. Ficaria muito grato pelo feedback construtivo!

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


All Articles