Developers Club geek daily blog

1 year, 3 months ago
Lori Timesheets — accounting of time on the CUBA platform


"Time is the capital of the worker of brainwork."
Honoré de Balzac


Often it happens that people give preference to old and usual things, ignoring new, even to itself to the detriment. Here and we long time with persistence used system of accounting of time which did not meet our requirements and constantly created problems literally all — from programmers to accounts department.
Lori Timesheets — accounting of time on the CUBA platform
General tortures with system of accounting of time, because of lack of time (cm drawing), did not become a strong reason for development of the system. The idea to write the real application for demonstration of opportunities of our CUBA platform rescued a situation. Combining business with pleasure, the system of accounting of time became the first candidate.

At the moment development is complete, the application is implemented in our company, and we are ready to share it with everyone.

In this article I will tell how we in a short time (< 1 мес), ограниченными силами (человек и еще полчеловека) разработали это приложение.

First steps


On established practices, starting system development, we describe data domain, defining entities which will appear in system. Such approach allows to evaluate in advance (though not precisely) estimated complexity of future system.

Let's think what entities will be necessary for us in system of accounting of time.
  • The user — it is clear where without user. Fortunately, such entity already is in a platform, we use it.
  • The client — that who pays money.
  • The project — what pay money for.
  • Task — a specific type of works.
  • Record of time — what is the time the user spent for execution of a certain task in specific day.


It would seem, it is enough. We had such set of entities in old system. For the convenience we had to add something.

  • The role on the project — projects is a lot of, the same user can be the manager of one project and the developer on another (at us so happens).
  • The participant of the project — communication of the project and the user, contains references to them and a role on this project.
  • Task type — a sign on which it is possible to integrate several tasks. For example the type of tasks "Testing" will allow us to understand how many we time spend for testing within the company and to compare this indicator for different projects.
  • Activity type — a sign on which it is possible to integrate several records of time. For example within a task of development of mobile application we do the analysis, we write a code, we write tests, fiksy bugs. If necessary, having added the corresponding types of activity to the project, we will be able to trace, was what is the time spent on each point.
  • The days off — that people were not mistaken, filling in time-sheets on holidays, we highlight such days in system using the set days off.
  • Tags — semistructured tags which can be added to records of time.
  • Types of tags — a sign in which certain tags are grouped.


We in article will not describe property of each entity, you can see them, having glanced in a project code.

Having decided on an object model, we began work in CUBA studio. For a start we created the corresponding entities. After that we seized the remarkable opportunity of autocode generation in studio, having received SQL scripts for creation of the database, and also standard screens (the screen of the list of entities and the screen of editing separate entity) with CRUD actions. For 80% of entities there were enough standard screens. We edited those screens which needed completion by means of WYSIWYG of the editor of screens.

Then we configured the main menu of the application that users had convenient access to necessary entities.
At that moment the application it was already possible to start and work with entities of an object model (to create/edit/delete).

In only several hours we created really working, full-function prototype of system of accounting of time.

We do the interface to more friendly


We received the working prototype, but it was very far from a working application.
Let's look what we wanted to achieve:
  1. Speed and convenience of filling of time-sheets
  2. Simplicity of setup of system
  3. Pleasant appearance


Input of time-sheets in a week


The first that it was necessary for us - it is convenient filling of time-sheets in a week. In old system there was such screen and we decided to make similar. Here what at us turned out.

Lori Timesheets — accounting of time on the CUBA platform

As you perhaps remember, in an object model there is no entity "The report in a week". It would be possible to implement this screen in general without any bindings to entities, however it is much more convenient to create intermediate not persistent entity which is not connected with the database directly, but all set of components works with it as with normal entity. Such acceptance allows to abstract from database structure and to create the screens more clear to the user. Such entity very simply looks:

@MetaClass(name = "ts$WeeklyReportEntry")
public class WeeklyReportEntry extends AbstractNotPersistentEntity {
    .....
    @MetaProperty(mandatory = true)
    protected Project project;
    @MetaProperty(mandatory = true)
    protected Task task;
    .....
}

In this case WeeklyReportEntry represents 1 line in the table.

We made the table edited that it was more convenient to fill hours on each day of the week. Besides, we added calculation of the sum of hours in columns, and also illumination of potentially incorrectly filled days. Then, at the request of users we added a feature to group entries in the table in the project and in a task. All this was made by means of standard mechanisms of the CUBA platform.

Input of time-sheets from a calendar


The screen of input of time-sheets from a calendar was the following on importance for us. Unfortunately, at the moment in a platform there is no Calendar component. However, it is in a framework of Vaadin which we use for drawing of the web client. Having inherited it and having slightly finished (it is described below), we used it in the application. We also added validation of potentially incorrectly completed time-sheets, illumination of holidays and the days off, summing of hours on weeks and month to a calendar.

Lori Timesheets — accounting of time on the CUBA platform

Setup of projects and tasks


Simplicity of setup of projects and tasks was one more purpose for us. In spite of the fact that we had basic screens of the project and a task, we decided to make the special screen which would make tincture easy and pleasant. Key requirements were: an opportunity to switch quickly between projects, an opportunity to quickly add people and a task to different projects. The decision to make the screen in the form of 3 connected tables was made: projects, tasks and participants of projects.

At the choice of the project in tables of tasks and participants the records corresponding to this project are shown. The CUBA platform allows to create the connected data sources for tables therefore no special code for this purpose was required. Actually, from 3 standard screens (the list of projects, the task list, the list of participants of projects) we collected one, replaced everything 3.

Lori Timesheets — accounting of time on the CUBA platform

"Command line"


The so-called command line became one more innovation which we decided to implement. It allows by means of input of simple text command to fill in time-sheets in a week and even for the whole month. It looks so:

Lori Timesheets — accounting of time on the CUBA platform

Also, by means of the Vaadin component which is called AceEditor we taught to do the command line of the hint. We will tell about it below.

It is necessary to notice that we spotted this remarkable concept in the Everhour system, having slightly finished it under the needs.

Rapid development


Naturally, all these screens were not developed by us for once.
As usual, bringing UI to mind took much more time, than creation of its first option. Here we were strongly helped by the mechanism of "warm boot" of changes on the server implemented in the CUBA platform. Thanks to it about 90% (~) of changes in UI do not need reset of the server. Moreover, there is an opportunity to reboot so logic of a kernel (services and bins). In more detail this mechanism is described in our article.

We expand client-side components


We add a calendar


As it was already told above, the calendar was necessary for us. Fortunately it was found in the list of the Vaadin components. Now I will tell as we added it.

Vaadin is constructed on GWT, but at the same time the Vaadin-component exists both on the client, and on the server. Usually there is also intermediate part which is used for communication of the client and the server. So that to expand a calendar, we should work both with server, and with a GWT code.

The calendar has a status com.vaadin.shared.ui.calendar.CalendarState. We want that the status stored, in addition, days which are considered as days off (it is configured in system), and holidays. For this purpose we inherit this class.

public class TimeSheetsCalendarState extends CalendarState {
    .....
    public Set<Integer> weekends = new HashSet<>();
    public Set<String> holidays = new HashSet<>();
    ....

}

Now we have to inherit the server class com.vaadin.ui.Calendar to fill new properties.
public class TimeSheetsCalendar extends Calendar {
   ....
   public TimeSheetsCalendar(CalendarEventProvider eventProvider) {
        super(eventProvider);

        getState().weekends = getWeekends();
    }

    @Override
    public void beforeClientResponse(boolean initial) {
        super.beforeClientResponse(initial);
        getState().holidays = getHolidays();
    }
    ....
}

After that we can inherit a widget of com.vaadin.client.ui.VCalendar and make so that it changed style of a cell depending on that, a holiday it or not.
public class TimeSheetsCalendarWidget extends VCalendar {

    protected Set<Integer> weekends = new HashSet<Integer>();
    protected Set<String> holidays = new HashSet<String>();

    protected boolean isWeekend(int dayNumber) {
        return weekends.contains(dayNumber);
    }

    protected boolean isHoliday(String date) {
        return holidays.contains(date);
    }

    @Override
    protected void setCellStyle(Date today, List<CalendarDay> days, String date, SimpleDayCell cell, int columns, int pos) {
        CalendarDay day = days.get(pos);
        if (isWeekend(day.getDayOfWeek()) || isHoliday(date)) {
            cell.addStyleName("holiday");
            cell.setTitle(date);
        }
    }

It was necessary only to expand the class com.vaadin.client.ui.calendar.CalendarConnector that it copied data on holidays and days off from a status in a widget.
@Connect(value = TimeSheetsCalendar.class, loadStyle = Connect.LoadStyle.LAZY)
public class TimeSheetsCalendarConnector extends CalendarConnector {

    @Override
    public TimeSheetsCalendarWidget getWidget() {
        return (TimeSheetsCalendarWidget) super.getWidget();
    }

    @Override
    public TimeSheetsCalendarState getState() {
        return (TimeSheetsCalendarState) super.getState();
    }

    @Override
    public void onStateChanged(StateChangeEvent stateChangeEvent) {
        getWidget().setWeekends(getState().weekends);
        getWidget().setHolidays(getState().holidays);
        super.onStateChanged(stateChangeEvent);
    }
}

As a result we can add TimeSheetsCalendar to any screen created on the CUBA platform.
public class CalendarScreen extends AbstractWindow {
    @Inject
    protected BoxLayout calBox;
    protected TimeSheetsCalendar calendar;
    ....
    protected void initCalendar() {
         ....
         calendar = new TimeSheetsCalendar(dataSource);
         ....
         AbstractOrderedLayout calendarLayout = WebComponentsHelper.unwrap(calBox);
         calendarLayout.addComponent(calendar);
    }

We add the hint to "command line"


That it was convenient to users to use "command line", we decided that it has to prompt input options.

In Vaadin there is an AceEditor component which is able to do it. It is used in a platform (WebSourceCodeEditor) to issue hints in JPQL requests (for example when editing request in the report).
We decided to simplify to ourselves life and instead of writing of a new component based on AceEditor expanded WebSourceCodeEditor.

First of all we expanded org.vaadin.aceeditor.SuggestionExtension, having registered in it RPC service which has to process application of the command line.
public class CommandLineSuggestionExtension extends SuggestionExtension {
    protected Runnable applyHandler;
    
    public CommandLineSuggestionExtension(Suggester suggester) {
        super(suggester);

        registerRpc(new CommandLineRpc() {
            @Override
            public void apply() {
                if (applyHandler != null) {
                    applyHandler.run();
                }
            }
        });
    }

    public void setApplyHandler(Runnable applyHandler) {
        this.applyHandler = applyHandler;
    }

    public Runnable getApplyHandler() {
        return applyHandler;
    }
}

Then queue of the platform class com.haulmont.cuba.web.gui.components.WebSourceCodeEditor came.
public class WebCommandLine extends WebSourceCodeEditor implements CommandLine {
    @Override
    public void setSuggester(Suggester suggester) {
        this.suggester = suggester;

        if (suggester != null &&suggestionExtension == null) {
            suggestionExtension = new CommandLineSuggestionExtension(new CommandLineSourceCodeEditorSuggester());
            suggestionExtension.extend(component);
            suggestionExtension.setShowDescriptions(false);
        }
    }

    protected class CommandLineSourceCodeEditorSuggester extends SourceCodeEditorSuggester {
    }

    public CommandLineSuggestionExtension getSuggestionExtension() {
        return (CommandLineSuggestionExtension) suggestionExtension;
    }
}

And at last client-side class org.vaadin.aceeditor.client.SuggesterConnector.
@Connect(CommandLineSuggestionExtension.class)
public class CommandLineSuggesterConnector extends SuggesterConnector {
    protected CommandLineRpc commandLineRpc = RpcProxy.create(
            CommandLineRpc.class, this);

    @Override
    public Command handleKeyboard(JavaScriptObject data, int hashId,
                                  String keyString, int keyCode, GwtAceKeyboardEvent e) {
        if (suggesting) {
            return keyPressWhileSuggesting(keyCode);
        }
        if (e == null) {
            return Command.DEFAULT;
        }

        if (keyCode == 13) {//Enter
            commandLineRpc.apply();
            return Command.NULL;//ignore enter
        } else if ((keyCode == 32 &&e.isCtrlKey())) {//Ctrl+Space
            startSuggesting();
            return Command.NULL;
        } else if ((keyCode == 50 &&e.isShiftKey())//@
                || (keyCode == 51 &&e.isShiftKey())//#
                || (keyCode == 52 &&e.isShiftKey())//$
                || (keyCode == 56 &&e.isShiftKey())) {//*
            startSuggestingOnNextSelectionChange = true;
            widget.addSelectionChangeListener(this);
            return Command.DEFAULT;
        }

        return Command.DEFAULT;
    }
}

In it we redefined behavior of the editor — hints have to appear except Ctrl-Space when clicking @,#,$,* (the hint of projects, tasks, tags, activity types). Clicking of Enter has to apply the command line (to fill in time-sheets).

We expand functionality of a platform


As you perhaps remember, we decided to use the class User provided by a platform. We wanted that it was possible to keep the number of obligatory working hours a week in record of the user. It is necessary for validation of the entered data (if the person specified in time-sheets more or less than has to). We had a choice — to create new entity which would refer to the sistmeny user, or to expand platform entity. For the purpose of economy of efforts we decided to go by expansion because this mechanism is quite simple (from the point of view of use) and perfectly works. Now I will show how we implemented this expansion.

First, it was necessary to make the successor of the class com.haulmont.cuba.security.entity.User and to add there a new field.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("Ext")
@Entity(name = "ts$ExtUser")
@Extends(User.class)
public class ExtUser extends User {
    ....
    @Column(name = "WORK_HOURS_FOR_WEEK", nullable = false)
    protected BigDecimal workHoursForWeek;
    public BigDecimal getWorkHoursForWeek() {
        return workHoursForWeek;
    }

    public void setWorkHoursForWeek(BigDecimal workHoursForWeek) {
        this.workHoursForWeek = workHoursForWeek;
    }
    ....
}

Then we created the screen expanding the screen of editing the user and registered it in system.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd" caption="msg://editCaption"
        class="com.haulmont.timesheets.gui.extuser.ExtUserEdit"
        extends="/com/haulmont/cuba/gui/app/security/user/edit/user-edit.xml"
        messagesPack="com.haulmont.timesheets.gui.extuser"
        xmlns:ext="http://schemas.haulmont.com/cuba/window-ext.xsd">
    <layout>
        <groupBox id="propertiesBox">
<h4></h4>            <grid id="propertiesGrid">
                <rows>
                    <row id="propertiesRow">
                        <fieldGroup id="fieldGroupRight">
                            <column>
                                <field id="workHoursForWeek"
                                       caption="msg://com.haulmont.timesheets.entity/ExtUser.workHoursForWeek"
                                       ext:index="5"/>
                            </column>
                        </fieldGroup>
                    </row>
                </rows>
            </grid>
        </groupBox>
    </layout>
</window>

Now instead of entity of User at system there is ExtUser and the screen of editing contains the workHoursForWeek field.

It is the simplest example of expansion of functionality if you want to learn about expansions more — read our article.

We do a distribution kit by the hands


From the very beginning we were going to make this system a product which other people will use. In order that installation and start of system were simple, we decided to make something like a distribution kit.

Our "distribution kit" represents the zip-file which contains the folder with the container of servlets Tomcat and scripts for a start/stop of system.

As for assembly of projects on a platform Gradle is used, to collect such "distribution kit" does not represent work.
def distribDir="./distrib"
def scriptsDir="./scripts"

task cleanTomcatLogs << {
    def dir = new File(tomcatDir, '/logs/')
    if (dir.isDirectory()) {
        ant.delete(includeemptydirs: true) {
            fileset(dir: dir, includes: '**/*')
        }
    }
}

task copyTomcat(type: Copy, dependsOn: ['setupTomcat',':app-core:deploy', ':app-web:deploy', ':app-web-toolkit:deploy', 'cleanTomcatLogs']) {
    from file("$tomcatDir/..")
    include "tomcat/**"
    into "$distribDir"
}

task copyLoriScripts(type: Copy) {
    from file("$scriptsDir")
    include "*lori.*"
    into "$distribDir"
}

task copyTomcatScripts(type: Copy, dependsOn: 'copyTomcat') {
    from file("$scriptsDir")
    include "*classpath.*"
    into "$distribDir/tomcat/bin/"
}

task buildDistributionZip(type: Zip, dependsOn: ['copyLoriScripts', 'copyTomcatScripts']) {
    from "$distribDir"
    exclude "*.zip"
    baseName = 'lori'
    version= "$artifactVersion"
    destinationDir = file("$distribDir")
}

task distribution(dependsOn: buildDistributionZip) << {
}

The only problem arose with Tomcat. He desperately did not want to start in system where the system JAVA_HOME variable is not set.

To force it to ignore lack of this variable, it was necessary to replace scripts of setclasspath.sh and setclasspath.bat with simpler.

Conclusion


There is a lot of products for accounting of working hours therefore probably there is a question why we wrote one more? Reasons a little. The main — we wanted that the product was most convenient in our field of activity (software development). Besides, we needed to provide simplicity of integration with other systems and completions under the changing processes. Well, and at last, we wanted to create the useful application which would become a good example of development on the CUBA platform.

The application is free, its code is available on github. The built-in license for the CUBA platform allows to work at the same time to 5 users. The lifelong license for unlimited number of users costs symbolical 300 rub.

We hope that Lori Timesheets will bring benefit not only to us, but also someone else. The open code and the mechanism of expansions will allow to adapt easily the application under themselves.

This article is a translation of the original post at habrahabr.ru/post/272231/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus