在许多组织中,使用KPI(关键绩效指标)进行单位评估。 在我工作的组织中,这样的系统称为“绩效指标体系”,在本文中,我想谈一谈我们如何在一个月内利用指标自动完成部分工作。 综上所述,我们的劳动力成本不是最大的,但与此同时,我们尝试实施一些长期存在的愿望清单。 在我的故事中,不会有任何大肆宣传的技术或启示(毕竟,省级发展是艰巨的),但是关于该主题的一些草图将有助于理解我们的开始方式,所做的工作以及从发展中得到的想法。 如果您还没有觉得厌烦,我请一只猫。
背景知识
早在2006年,我们就引入了绩效指标体系:制定了15条评估标准,一种计算指标的方法,并确定了每季度计算这些指标的频率。 评估是对位于我们地区所有地区的该组织的22个分支机构进行的。 为了使指标充满热情地获得实现,必须给他们附加奖金-指标的总和越高,评级中的排名越高,评级中的排名越高,每个季度和每年的保费就越高。
随着时间的流逝,标准的组成发生了变化;每个季度都添加了新的标准,或者排除了旧的标准。 在大约2016年的顶峰时期,指标数量超过了40,而现在只有14个。
但是,所有这些时间的计算过程都是相同的。 每个标准由上级组织的负责单位根据专门为此标准批准的算法来计算。 该计算既可以通过简单的公式进行,也可以通过许多复杂的公式进行,它可能需要从多个系统中收集数据,而算法很可能会经常更改。 然后,一切都已经变得更加简单:将计算得出的百分比指标乘以其系数,从而通过准则获得一个点,然后对这些点进行排名,并根据该点进行每个单位的计算。 用总和来计算最终位置也可以做同样的事情。
从一开始,Excel就被用来考虑和计算上述所有内容。 多年以来,最终的一套标准以及对点和地点的进一步计算是在一块漂亮的盘子中进行的,图中显示了其中的一部分。

我只注意到,在这款出色的平板电脑中,大多数扬声器只是隐藏起来,但实际上在2016年第4季度看起来像这样

同意,如此多的指标和如此多的表格并没有增加其简单性。 此外,每个标准都是单独计算的,总务部的女孩则亲自制作了此汇总表。 考虑到不仅标准的组成发生了变化,而且计算可能会重做几次(由于许多原因),所以,每个季度的最后几天(不客气地说)都不高兴。 我不得不不断地更改表中的数据,检查并再次检查它:这样,您忘记在点的总和上添加新列,或者您不更新系数,并且对于某些人来说,位置变低了,因此奖金减少了。 因此,在对表进行英勇的表述之后,所有考虑了每个标准,管理人员,甚至是组织分配的IT专家的所有人都开始了英勇的校验,检查和同意。 考虑到所有工作都是手动完成的,并且数据透视表的指标计算已发送到邮件中,因此很多情况下,数据透视表的公式和相关版本的数据中包含错误。 证据队长报告说,在检测到每个错误之后,将重新启动验证过程。
计算完所有指标后,便会批准数位板并将其发送到分支机构,在分支机构对其进行双重检查:如果阴险的Excel认为出了什么问题该怎么办? 我认为很明显,就标准制定年度报告或分析历史数据显然是令人兴奋的追求。
但是,从管理的角度来看,这样的系统非常有效,并且在必要时允许收紧工作的下垂区域,监视并了解特定工作区域中每个分支的状况。 此外,根据标准计算的分支位置是一个整体指标。
系统必须改变
实践证明了评估系统的必要性和重要性。 在2017年初,很明显,每季度计算一次标准可以评估已完成的工作,但对其进行监控使其很薄弱。 时间间隔太长。 在这方面,决定每两周进行一次指标计算,并在同一季度将结果汇总。 计算频率的提高确实使分支机构经理能够快速响应更改并增强对其单位内流程的控制。
但是仅在上级组织中,很少有人对执行上述数据收集过程的前景感到满意,而不是每个季度而是每两周一次。 由于不难猜测,他们决定自动化该过程。 截止日期很紧:从决定改用新的计算频率之时到实际的过渡,只需要一个月的时间。 在此期间,非常需要提供比一堆Excel更好的代码和计算工具,以及用于收集和通知的邮件。
最初,对于真正需要自动完成的工作以及指标本身的计算(考虑到所有公式和数据源)进行了很多激烈的讨论。 但是鉴于紧迫的期限,此类功能的复杂性以及在当前状态下对其进行持续维护的需求设法满足了系统的以下要求:
- 应保留标准参考手册,以保留其更改历史。
- 应该可以输入和存储计算出的指标,以及将它们转换为点的方式与在同一板块中一样;
- 根据指标的价值,应生成一份报告,所有有关方面均可获取;
- 自然,所有这些都应该提供一个Web界面。
功能很小,但是时间不多。
开发开始
快速开发和自动生成界面的方式一直吸引着我。 在需要实现CRUD功能的情况下,可以开箱即用地提供接口和部分业务逻辑的想法似乎很诱人。 当然,该接口将是朴实无华且笨拙的逻辑,但是对于许多任务而言,这就足够了。
在这些想法的驱使下,我决定尝试类似的方法。 有Spring Roo,Cuba和其他有趣的工具,但选择权却落在了OpenXava上。 首先,一旦我在上面做了一个非常简单的应用程序并感到满意,其次,当时的这个框架非常成功地适合了我们的技术堆栈。 另外,有一个简短的俄语教程非常令人高兴。
可以在
此处找到有关OpenXava的功能的简要说明。 OpenXava是一个框架,可实现自动构建与基于JPA的数据库集成的Web界面,并使用注释来描述可视化规则。 该应用程序基于业务组件-包含创建应用程序所需信息的Java类。 这些信息包括数据结构,验证器,有效表示形式,映射到数据库表。 业务组件的操作是通过控制器执行的,这些控制器可以从框中进行CRUD,搜索,导出为PDF等。 OpenXava应用程序提供了一组模块。 模块将业务组件与一个或多个控制器关联。 为每个业务组件定义的表示用于显示界面。 毫不奇怪,MVC具有自己的氛围。
资料储存
在大多数应用程序中,我们使用IBM DB2 DBMS。 创建了一个小型数据库,该数据库存储准则组和准则的参考书,通过这些准则和准则评估分支,分支目录和输入准则的计算值的标牌。 对于每个标准,在某个时间点分配一个系数,该系数将用于评分。 对于每个标准,每个分支机构也会在特定日期接收评估。 有关标准值的数据是历史存储的,即与任何日期相关的数据将是过去的最近日期输入的数据。 这种方法受到1C:Enterprise信息记录的启发,对我来说似乎足够方便:存在历史,并且编辑/删除问题并不是特别猛烈。
数据库结构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 );
为了从数据库中获得令人垂涎的分支性能报告,创建了存储的函数,其结果已经映射到Java类。 函数看起来像这样。
首先,我们获得该日期有效的所有条件,以及与该日期相关的条件系数 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 ;
现在,我们将在同一日期收到所有分支的所有条件的值 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 ;
在最后一个查询中,我们对指标的值进行编号,对它们进行排名并找到最小值和最大值,这是以后计算位置所必需的 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
将部分业务逻辑转移到DBMS是很合理的,尤其是在为不同报告准备数据时。 事实证明,可以用更简洁自然的形式来编写操作;在Java中,对数据的这种操作将需要大量的代码并需要进行一些结构化。 尽管相对复杂或不平凡的操作仍然更易于用Java编程。 因此,在我们的应用程序中,关于数据采样,使用一种方法,其中在存储的函数和过程中执行数据集,裁剪条件和某些可以由窗口函数执行的操作的连接,并在应用程序中实现更复杂的逻辑。
应用程式
如我所说,OpenXava用于实现该应用程序。 要立即使用典型的界面和CRUD,您需要执行一些操作。
首先,在web.xml中,您需要从导航应用程序的插件中描述过滤器和servlet:
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>
接下来,在controllers.xml文件中,我们定义应用程序中使用的控制器。 在我们的例子中,最简单的就足够了:
controllers.xml <controllers> <controller name="Typical_View"> <extends controller="Navigation"/> <extends controller="CRUD"/> <extends controller="ExtendedPrint"/> </controller> </controllers>
上述控制器默认情况下结合了OpenXava中包含的控制器的功能,这些功能很容易从名称中猜测出来。
最后,在application.xml文件中,我们将连接创建的控制器和模型。 像这样:
application.xml <application name="summar"> <module name="RegValueCriteria"> <model name="RegValueCriteria"/> <controller name="Typical_View"/> </module> </application>
如上所述,应用程序基于构成应用程序模型的业务组件。 例如,在application.xml中考虑与控制器关联的RegValueCriteria组件。 此组件描述了分支标准的值(为简便起见,仅保留对类字段的描述,而我将省略诸如getter和setter的方法):
组件类 @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; }
除了常用的JPA批注。 您还可以注意到OpenXava批注。 应该更详细地考虑它们。
@View
允许
@View
控制类字段的表示格式,并借助方括号形式的特殊语法,可以将字段组合成组,并使用符号和进行水平和垂直排列
;
。 如果需要为单个组件指定多个映射,则使用
@View
注释将
@View
注释分组。 在我们的示例中,属性安排如下:
@View(members = "idCriteria [idCriteria];" + "idGroupCriteria [idGroupCriteria];" + "idDepart [idDepart];" + "data [dateReg, percent, val]")
它看起来像这样:

朴实无华,但花费最少的精力。 但是,有一些方法可以“重新激活”表单。
为了在创建组件时填写注册日期,使用
@DefaultValueCalculator
批注,该批注调用特殊的计算器对象。 在这里,使用的是OpenXava本身的计算器,但您也可以自定义计算器。
@Stereotype
批注用于通过适当的控件显示日期。
要配置包含相关对象的下拉列表,请使用
@DescriptionsList
批注,您可以在其中指定将在列表中显示的属性。
使用注释,您可以实现表单本身的一些业务逻辑。 例如,为了使百分比发生变化,可以在考虑标准系数的情况下自动计算值,您可以为
BigDecimal val
字段应用
@OnChange
注释。 为了使
@OnChange
批注起作用,它需要指向实现
OnChangePropertyBaseAction
接口的类。 应该在该类中实现一个
execute()
方法,其中从视图中获取输入数据,执行计算并将计算出的值写回到视图中:
子类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); } } } } }
对于表格的数据表示,使用
@Tab
批注,它允许您列出将以表格表示形式显示的对象的那些属性。 在我们的示例中,注释的安排如下:
@Tab(properties= "idDepart.name, idCriteria.name, idGroupCriteria.name, dateReg, percent, val" )
它看起来如下

对过滤器的可用性,分页和开箱即用感到满意,但是,许多细节需要使用文件进行完善。
与其他组件的工作方式也类似。 使用OpenXava大大降低了实现CRUD功能和大多数用户界面的人工成本。 如果您没有发现细节方面的问题,并且尝试实现比带有多个事件的输入表单更复杂的内容,则使用预定义的控制器的操作和注释来构建表单可以大大节省时间。 尽管经验上可能是这样。
到底是什么
还记得该应用程序的用途吗? 是的,因此带有指示器的板不会在Excel中英勇地减少,而是会根据输入的数据自动创建。 在浏览器窗口中,数据透视表开始如下所示:


我不会提供实现的详细信息,因为一切都不太好,并且JSP与在请求数据时生成的HTML的混合并不能与公众分享。 同时,上面演示了数据采样本身。
但是,我想讲一个有趣的细节。 当收集到应用程序的需求时,管理层确实希望除了摘要报告之外,还可以以区域地图的形式显示单个指标的值,将其划分为区域,以指示相应分支的位置和得分。 谁识别了地图上的区域-做得好=)

一方面,该要求是可选的,但另一方面,该图片承诺是可视的,并且从实现的角度来看,尝试是很有趣的。 经过一番思考,这个想法浮出水面,以SVG格式找到该区域的图像,并从中制作出XSLT模板。
生成的模板很容易用数据填充,然后将其转换为PNG。
首先,使用上述查询,对数据进行采样,将接收到的数据转换为此类的对象:
用于将数据输出到地图的类 @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; } } }
接下来,将对象转换为XML;
转换次数 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(); }
现在,让我们使用生成的XML,准备好的XSLT模板,应用转换并在输出中获取svg:
取得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)));
原则上,我们可以到此为止,浏览器可以毫无问题地显示SVG。 但是所描述的报告也是通过Telegram机器人获得的,因此SVG必须转换为某种格式,例如JPEG或PNG。 为此,请使用
Apache Batik转换为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; }
地图形式的报告已准备就绪。 也可以通过浏览器查看它,并向电报机器人请求。 我认为还不错。
结论
到约定的时间,我们已经有时间了,并且已经在2017年3月开始,将绩效指标而非Excel开始定期输入到创建的系统中。 一方面,最大的问题尚未解决;计算指标最困难的部分是手动完成。 但是,另一方面,执行这些计算带来了不断改进的风险。 此外,即使是创建的用于收集数据的简单界面,也消除了许多问题,这些问题具有不断的变化,版本控制和Excel电子表格的摘要。 大量的手动工作,检查和复查已被删除。
不能不说Open Xav上的界面不太吸引用户。 首先,对其功能有很多疑问。 在某些时候,用户开始抱怨输入数据花费了太多时间,并且通常,“就像在Excel中一样,我们只需要一个程序。” 我什至不得不基于记录创建时的数据来监视输入速度。 此监视表明,即使在最严重的情况下,用户在输入上花费的时间也不会超过15分钟,但通常需要5-7分钟,尽管他们需要在22个分支上输入数据。 这样的指标似乎是可以接受的。
但是,我要注意两件事:
- 实践证明,Open Xava是快速创建界面的好工具。 我什至会说一个原型接口。 毫无疑问,它的优点是一般的秩序和规律性。 应用程序中的所有表单都是根据统一的原则创建的,这使开发人员不必拿出不需要的自行车,而是让用户处理标准的表单集。 但是,尝试实现更复杂的逻辑或更改自己的控制权导致我们遇到了许多在分配的时间内无法解决的问题。 最有可能的是,我们只是不了解,目标是以最小的努力创建一个简单的CRUD接口。 对我自己而言,我认为Open Xava是一个有趣的工具,在其中可以轻松地完成简单的事情,但是如果您需要做一些复杂的事情,我宁愿花费更多的时间使用ExtJS或React创建客户端部分,但具有更大的灵活性。
- 即使是自信的用户也很难使用新界面。 这当然不是秘密。 我认为,这主要是由于对许多接口的系统性缺乏了解。 对于许多人来说,任何应用程序都有一组屏幕,每个屏幕都是唯一的,并且按照其自身未知的原理工作:例如,有一个包含对象/行列表的表单(列表表单),但是对于这么多的用户,应用程序中的每个这样的表单都可以统一的过滤,分页,排序功能和通常相同的行为,并辅以特定功能。 事实是,大量的公司软件几乎都是自发的按钮和表格,并以“单击此按钮”的风格含混不清的文档而变得更加复杂。 从这个角度来看,由相同的Open Xav规范的用户开发人员创建的界面会在头上创建更多的顺序。 的确,采用这种方法,魔术按钮不会在任何地方消失,而是会整齐地布置在形状中。
如果我们谈论整个组织的利益,那么在引入应用程序之后,正如预期的那样,控制程度会提高。 现场领导者获得了一种监控指标的工具,他们可以在更操作的模式下影响工作,比较不同时期的指标,而不会与已发送文件的流程混淆。 我们以一种非常有趣的方式发现了领导者对其分支机构进行持续而警惕的监视的兴趣:在查看Telegram机器人日志时,我们注意到一些报告是在凌晨4点或5点收到的。 您可以立即看到谁对工作感兴趣。
仅此而已。 谢谢您的建设性反馈!