modules/global/src/com/haulmont/timesheets/entity/Task.java
@NamePattern("[%s] %s|project,name")
@Table(name = "TS_TASK")
@Entity(name = "ts$Task")
public class Task extends StandardEntity {

    private static final long serialVersionUID = 4693836896751773146L;

    @Column(name = "NAME", nullable = false, length = 100)
    protected String name;

    @Column(name = "CODE", nullable = false, length = 50)
    protected String code;

    @Column(name = "DESCRIPTION")
    protected String description;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "PROJECT_ID")
    protected Project project;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TYPE_ID")
    protected TaskType type;

    @Column(name = "STATUS", nullable = false)
    protected String status;

    @JoinTable(name = "TS_TASK_TAG_TYPE_LINK",
            joinColumns = @JoinColumn(name = "TASK_ID"),
            inverseJoinColumns = @JoinColumn(name = "TAG_TYPE_ID"))
    @ManyToMany
    protected Set<TagType> requiredTagTypes;

    @JoinTable(name = "TS_TASK_TAG_LINK",
            joinColumns = @JoinColumn(name = "TASK_ID"),
            inverseJoinColumns = @JoinColumn(name = "TAG_ID"))
    @ManyToMany
    protected Set<Tag> defaultTags;

    @JoinTable(name = "TS_TASK_PROJECT_PARTICIPANT_LINK",
            joinColumns = @JoinColumn(name = "TASK_ID"),
            inverseJoinColumns = @JoinColumn(name = "PROJECT_PARTICIPANT_ID"))
    @ManyToMany
    protected Set<ProjectParticipant> exclusiveParticipants;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToMany(mappedBy = "task")
    protected Set<TimeEntry> timeEntries;

    public void setExclusiveParticipants(Set<ProjectParticipant> exclusiveParticipants) {
        this.exclusiveParticipants = exclusiveParticipants;
    }

    public Set<ProjectParticipant> getExclusiveParticipants() {
        return exclusiveParticipants;
    }


    public TaskStatus getStatus() {
        return status == null ? null : TaskStatus.fromId(status);
    }

    public void setStatus(TaskStatus status) {
        this.status = status == null ? null : status.getId();
    }

    public void setRequiredTagTypes(Set<TagType> requiredTagTypes) {
        this.requiredTagTypes = requiredTagTypes;
    }

    public Set<TagType> getRequiredTagTypes() {
        return requiredTagTypes;
    }

    public void setTimeEntries(Set<TimeEntry> timeEntries) {
        this.timeEntries = timeEntries;
    }

    public Set<TimeEntry> getTimeEntries() {
        return timeEntries;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public void setDefaultTags(Set<Tag> defaultTags) {
        this.defaultTags = defaultTags;
    }

    public Set<Tag> getDefaultTags() {
        return defaultTags;
    }

    public TaskType getType() {
        return type;
    }

    public void setType(TaskType type) {
        this.type = type;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setProject(Project project) {
        this.project = project;
    }

    public Project getProject() {
        return project;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @MetaProperty(related = {"defaultTags"})
    public String getDefaultTagsList() {
        if (!PersistenceHelper.isLoaded(this, "defaultTags")) {
            return null;
        }

        if (defaultTags != null) {
            StringBuilder stringBuilder = new StringBuilder();
            for (Tag defaultTag : defaultTags) {
                stringBuilder.append(defaultTag.getInstanceName()).append(",");
            }
            return StringUtils.chop(stringBuilder.toString());
        }

        return "";
    }

    @MetaProperty(related = {"requiredTagTypes"})
    public String getRequiredTagTypesList() {
        if (!PersistenceHelper.isLoaded(this, "requiredTagTypes")) {
            return null;
        }

        if (requiredTagTypes != null) {
            StringBuilder stringBuilder = new StringBuilder();
            for (TagType requiredTagType : requiredTagTypes) {
                stringBuilder.append(requiredTagType.getInstanceName()).append(",");
            }
            return StringUtils.chop(stringBuilder.toString());
        }

        return "";
    }
}
modules/global/src/com/haulmont/timesheets/global/WorkdaysTools.java
@Component(WorkdaysTools.NAME)
public class WorkdaysTools {

    public static final String NAME = "ts_WorkdaysTools";

    @Inject
    protected WorkTimeConfigBean workTimeConfigBean;
    @Inject
    protected ProjectsService projectsService;

    public boolean isHoliday(Date date) {
        return !projectsService.getHolidaysForPeriod(date, date).isEmpty();
    }

    public boolean isWeekend(Date date) {
        DayOfWeek day = DayOfWeek.fromCalendarDay(DateTimeUtils.getCalendarDayOfWeek(date));
        return workTimeConfigBean.getWeekends().contains(day);
    }

    public boolean isWorkday(Date date) {
        return !isWeekend(date) && !isHoliday(date);
    }
}
modules/gui/src/com/haulmont/timesheets/gui/timeentry/AllTimeEntries.java
@UiController("ts$TimeEntry.all")
@UiDescriptor("timeentry-all.xml")
public class AllTimeEntries extends StandardLookup<TimeEntry> {

    @Inject
    protected SecurityAssistant securityAssistant;
    @Inject
    protected UserSession userSession;

    @Inject
    protected CollectionLoader<TimeEntry> timeEntriesDl;
    @Inject
    protected CollectionContainer<TimeEntry> timeEntriesDc;
    @Inject
    protected ScreenBuilders screenBuilders;
    @Inject
    protected GroupTable<TimeEntry> timeEntriesTable;

    @Subscribe
    protected void onInit(InitEvent e) {
        if (securityAssistant.isSuperUser()) {
            timeEntriesDl.setQuery("select e from ts$TimeEntry e");
        } else {
            timeEntriesDl.setParameter("user", userSession.getUser());
        }
    }

    @Subscribe("timeEntriesTable.create")
    protected void onTimeEntriesTableCreateActionPerformed(Action.ActionPerformedEvent e) {
        screenBuilders.editor(timeEntriesTable)
                .newEntity()
                .withLaunchMode(OpenMode.DIALOG)
                .build()
                .show();
    }

    @Subscribe("timeEntriesTable.edit")
    protected void onTimeEntriesTableEditActionPerformed(Action.ActionPerformedEvent e) {
        screenBuilders.editor(timeEntriesTable)
                .withLaunchMode(OpenMode.DIALOG)
                .build()
                .show();
    }

    @Install(to = "timeEntriesTable", subject = "styleProvider")
    protected String timeEntriesTableStyleProvider(TimeEntry entity, String property) {
        if ("status".equals(property)) {
            if (entity != null) {
                return ScreensHelper.getTimeEntryStatusStyle(entity);
            }
        }
        return null;
    }

    @Subscribe
    protected void onBeforeShow(BeforeShowEvent e) {
        timeEntriesDl.load();
    }
}
modules/global/src/com/haulmont/timesheets/entity/TimeEntry.java
@Listeners("ts_TimeEntryListener")
@NamePattern("#getCaption|timeInMinutes")
@Table(name = "TS_TIME_ENTRY")
@Entity(name = "ts$TimeEntry")
public class TimeEntry extends StandardEntity implements TimeEntryBase {

    private static final long serialVersionUID = -5042871389501734493L;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "TASK_ID")
    protected Task task;

    @Column(name = "TASK_NAME", length = 100)
    protected String taskName;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "USER_ID")
    protected User user;

    @JoinTable(name = "TS_TIME_ENTRY_TAG_LINK",
            joinColumns = @JoinColumn(name = "TIME_ENTRY_ID"),
            inverseJoinColumns = @JoinColumn(name = "TAG_ID"))
    @ManyToMany
    protected Set<Tag> tags;

    @Temporal(TemporalType.DATE)
    @Column(name = "DATE_", nullable = false)
    protected Date date;

    @Column(name = "TIME_IN_MINUTES", nullable = false)
    protected Integer timeInMinutes = 0;

    @Column(name = "STATUS", nullable = false)
    protected String status = TimeEntryStatus.NEW.getId();

    @Column(name = "DESCRIPTION")
    protected String description;

    @Column(name = "REJECTION_REASON")
    protected String rejectionReason;

    @OnDeleteInverse(DeletePolicy.DENY)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ACTIVITY_TYPE_ID")
    protected ActivityType activityType;

    @Column(name = "TIME_IN_HOURS", nullable = false, precision = 10, scale = 2)
    protected BigDecimal timeInHours;

    @MetaProperty
    @Transient
    protected BigDecimal overtimeInHours;

    public void setTimeInHours(BigDecimal timeInHours) {
        this.timeInHours = timeInHours;
    }

    public BigDecimal getTimeInHours() {
        return timeInHours;
    }

    public void setTimeInMinutes(Integer timeInMinutes) {
        this.timeInMinutes = timeInMinutes;
    }

    public Integer getTimeInMinutes() {
        return timeInMinutes;
    }


    public void setActivityType(ActivityType activityType) {
        this.activityType = activityType;
    }

    public ActivityType getActivityType() {
        return activityType;
    }


    public TimeEntryStatus getStatus() {
        return status == null ? null : TimeEntryStatus.fromId(status);
    }

    public void setStatus(TimeEntryStatus status) {
        this.status = status == null ? null : status.getId();
    }

    public Set<Tag> getTags() {
        return tags;
    }

    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public String getTaskName() {
        return taskName;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Date getDate() {
        return date;
    }

    public void setTask(Task task) {
        this.task = task;
    }

    public Task getTask() {
        return task;
    }

    public void setRejectionReason(String rejectionReason) {
        this.rejectionReason = rejectionReason;
    }

    public String getRejectionReason() {
        return rejectionReason;
    }

    public String getCaption() {
        return HoursAndMinutes.fromTimeEntry(this).getFormattedCaption();
    }

    public HoursAndMinutes getSpentTime(){
        return HoursAndMinutes.fromTimeEntry(this);
    }

    public BigDecimal getOvertimeInHours() {
        return overtimeInHours;
    }

    public void setOvertimeInHours(BigDecimal overtimeInHours) {
        this.overtimeInHours = overtimeInHours;
    }

    @MetaProperty(related = {"user", "date"})
    public Overtime getOvertime() {
        if (!PersistenceHelper.isLoaded(this, "user")
                || !PersistenceHelper.isLoaded(this, "date")) {
            return null;
        }

        return new Overtime(getUser(), getDate(), overtimeInHours);
    }

    public void setOvertime(Overtime overtimeInHours) {
        this.overtimeInHours = overtimeInHours != null ? overtimeInHours.overtimeInHours : null;
    }
}
modules/global/src/com/haulmont/timesheets/entity/Project.java
@NamePattern("%s|name")
@Table(name = "TS_PROJECT")
@Entity(name = "ts$Project")
public class Project extends StandardEntity {

    private static final long serialVersionUID = 1282645826386756072L;

    @Column(name = "NAME", nullable = false, length = 100)
    protected String name;

    @Column(name = "CODE", nullable = false, length = 50)
    protected String code;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PARENT_ID")
    protected Project parent;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "CLIENT_ID")
    protected Client client;

    @Column(name = "DESCRIPTION")
    protected String description;

    @Column(name = "STATUS", nullable = false)
    protected String status;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToMany(mappedBy = "project")
    protected Set<ProjectParticipant> participants;

    @OneToMany(mappedBy = "project")
    protected Set<Task> tasks;

    @Column(name = "TIME_ENTRY_NAME_PATTERN", length = 500)
    protected String timeEntryNamePattern;

    public void setTimeEntryNamePattern(String timeEntryNamePattern) {
        this.timeEntryNamePattern = timeEntryNamePattern;
    }

    public String getTimeEntryNamePattern() {
        return timeEntryNamePattern;
    }


    public ProjectStatus getStatus() {
        return status == null ? null : ProjectStatus.fromId(status);
    }

    public void setStatus(ProjectStatus status) {
        this.status = status == null ? null : status.getId();
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public void setParticipants(Set<ProjectParticipant> participants) {
        this.participants = participants;
    }

    public Set<ProjectParticipant> getParticipants() {
        return participants;
    }

    public void setTasks(Set<Task> tasks) {
        this.tasks = tasks;
    }

    public Set<Task> getTasks() {
        return tasks;
    }

    public void setParent(Project parent) {
        this.parent = parent;
    }

    public Project getParent() {
        return parent;
    }

    public void setClient(Client client) {
        this.client = client;
    }

    public Client getClient() {
        return client;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
modules/gui/src/com/haulmont/timesheets/gui/util/TimeEntryOvertimeAggregation.java
public class TimeEntryOvertimeAggregation implements AggregationStrategy<Overtime, BigDecimal> {
    @Override
    public BigDecimal aggregate(Collection<Overtime> propertyValues) {
        Map<Map<String, Object>, BigDecimal> map = new LinkedHashMap<>();
        for (Overtime propertyValue : propertyValues) {
            Map<String, Object> key = new TreeMap<>();
            key.put("user", propertyValue.getUser());
            key.put("date", propertyValue.getDate());
            BigDecimal overtimeInHours = propertyValue.getOvertimeInHours();
            map.put(key, overtimeInHours != null ? overtimeInHours : BigDecimal.ZERO);
        }

        BigDecimal result = BigDecimal.ZERO;

        for (Map.Entry<Map<String, Object>, BigDecimal> entry : map.entrySet()) {
            result = result.add(entry.getValue());
        }

        return result;
    }
    @Override
    public Class<BigDecimal> getResultClass() {
        return BigDecimal.class;
    }
}
modules/global/src/com/haulmont/timesheets/entity/TimeEntryStatus.java
public enum TimeEntryStatus implements EnumClass<String> {
    NEW("new"),
    APPROVED("approved"),
    REJECTED("rejected"),
    CLOSED("closed");

    private String id;

    TimeEntryStatus(String value) {
        this.id = value;
    }

    public String getId() {
        return id;
    }

    public static TimeEntryStatus fromId(String id) {
        for (TimeEntryStatus at : TimeEntryStatus.values()) {
            if (at.getId().equals(id)) {
                return at;
            }
        }
        return null;
    }
}
modules/global/src/com/haulmont/timesheets/global/WorkTimeConfigBean.java
@Component(WorkTimeConfigBean.NAME)
public class WorkTimeConfigBean {

    public static final String NAME = "ts_WorkTimeConfigBean";

    @Inject
    protected WorkTimeConfig workTimeConfig;

    public BigDecimal getWorkHourForDay() {
        return getWorkHourForWeek().divide(BigDecimal.valueOf(getWorkDaysCount()), BigDecimal.ROUND_HALF_UP);
    }

    public BigDecimal getWorkHourForWeek() {
        return workTimeConfig.getWorkHourForWeek();
    }

    public BigDecimal getUserWorkHourForDay(User user) {
        return getUserWorkHourForWeek(user).divide(BigDecimal.valueOf(getWorkDaysCount()), BigDecimal.ROUND_HALF_UP);
    }

    public BigDecimal getUserWorkHourForWeek(User user) {
        if (user instanceof ExtUser) {
            ExtUser extUser = (ExtUser) user;
            return extUser.getWorkHoursForWeek();
        }
        return BigDecimal.ZERO;
    }

    public void setWorkHourForWeek(BigDecimal hours) {
        workTimeConfig.setWorkHourForWeek(hours);
    }

    public int getWorkDaysCount() {
        return getWorkDays().size();
    }

    public List<DayOfWeek> getWorkDays() {
        List<String> days = workTimeConfig.getWorkDays();
        if (days.isEmpty()) {
            return Collections.emptyList();
        }
        List<DayOfWeek> workDays = new ArrayList<>(days.size());
        for (String day : days) {
            workDays.add(DayOfWeek.fromAbbreviation(day));
        }
        return Collections.unmodifiableList(workDays);
    }

    public void setWorkDays(Collection<DayOfWeek> workDays) {
        if (workDays.isEmpty()) {
            workTimeConfig.setWorkDays(Collections.emptyList());
            return;
        }
        int count = 3;
        List<String> days = new ArrayList<>(workDays.size());
        for (DayOfWeek day : workDays) {
            days.add(day.getId().substring(0, count));
        }
        workTimeConfig.setWorkDays(days);
    }

    public List<DayOfWeek> getWeekends() {
        List<DayOfWeek> days = new ArrayList<>(Arrays.asList(DayOfWeek.values()));
        for (DayOfWeek day : getWorkDays()) {
            days.remove(day);
        }
        return Collections.unmodifiableList(days);
    }

    public Date getOpenPeriodStart() {
        return workTimeConfig.getOpenPeriodStart();
    }

    public void setOpenPeriodStart(Date date) {
        workTimeConfig.setOpenPeriodStart(date);
    }
}
modules/global/src/com/haulmont/timesheets/service/ProjectsService.java
public interface ProjectsService {
    String NAME = "ts_ProjectsService";

    List<Project> getProjectChildren(Project parent);

    @Nullable
    ProjectRole getUserProjectRole(Project project, User user);

    @Nullable
    <T extends Entity> T getEntityByCode(Class<T> clazz, String code, @Nullable String viewName);

    <T extends Entity> MetaPropertyPath getEntityMetaPropertyPath(Class<T> clazz, String property);

    List<TimeEntry> getTimeEntriesForPeriod(
            Date start, Date end, User user, @Nullable TimeEntryStatus status, @Nullable String viewName);

    List<TimeEntry> getApprovableTimeEntriesForPeriod(
            Date start, Date end, User approver, User user, @Nullable TimeEntryStatus status, @Nullable String viewName);

    List<Holiday> getHolidaysForPeriod(Date start, Date end);

    List<Task> getActiveTasksForUser(User user, @Nullable String viewName);

    Map<String, Task> getActiveTasksForUserAndProject(User user, Project project, @Nullable String viewName);

    List<ActivityType> getActivityTypesForProject(Project project, @Nullable String viewName);

    List<Project> getActiveProjectsForUser(User user, @Nullable String viewName);

    List<Project> getActiveManagedProjectsForUser(User user, @Nullable String viewName);

    boolean assignUsersToProjects(Collection<User> users, Collection<Project> projects, ProjectRole projectRole);

    List<Tag> getTagsForTheProject(@Nullable Project project, @Nullable String viewName);

    List<Tag> getTagsWithTheTagType(TagType type, @Nullable String viewName);

    List<ProjectParticipant> getProjectParticipants(Project project, @Nullable String viewName);

    List<User> getProjectUsers(Project project, @Nullable String viewName);

    List<User> getManagedUsers(User manager, String viewName);
}
modules/gui/src/com/haulmont/timesheets/gui/util/ScreensHelper.java
public class ScreensHelper {
    public static final String COMMON_DAY_CAPTION_STYLE = "%s %d";
    public static final String TODAY_CAPTION_STYLE = "<strong><span style=\"text-decoration: underline;\">%s</span></strong>";
    public static final String HOLIDAY_CAPTION_STYLE = "<font color=\"#EF525B\">%s</font>";

    protected static Messages messages = AppBeans.get(Messages.NAME);
    protected static TimeSource timeSource = AppBeans.get(TimeSource.NAME);
    protected static WorkdaysTools workdaysTools = AppBeans.get(WorkdaysTools.NAME);
    protected static UiComponents uiComponents = AppBeans.get(UiComponents.NAME);
    protected static Metadata metadata = AppBeans.get(Metadata.NAME);

    public static class EntityCodeGenerationListener<E extends Entity> implements Consumer<InstanceContainer.ItemPropertyChangeEvent<E>> {
        @Override
        public void accept(InstanceContainer.ItemPropertyChangeEvent<E> e) {
            E source = e.getItem();
            if ("name".equalsIgnoreCase(e.getProperty()) && source.getMetaClass().getProperty("code") != null) {
                String codeValue = source.getValue("code");
                if (StringUtils.isBlank(codeValue)) {
                    String newName = String.valueOf(e.getValue());
                    String newCode = newName.toUpperCase().replaceAll(" ", "_");
                    source.setValue("code", newCode);
                }
            }
        }
    }

    public static AggregationInfo createAggregationInfo(MetaPropertyPath path, AggregationStrategy strategy) {
        AggregationInfo info = new AggregationInfo();
        info.setPropertyPath(path);
        info.setStrategy(strategy);
        return info;
    }

    public static String getTaskStatusStyle(Task task) {
        switch (task.getStatus()) {
            case ACTIVE:
                return "task-active";
            case INACTIVE:
                return "task-inactive";
            default:
                return null;
        }
    }

    public static String getTimeEntryStatusStyle(TimeEntry timeEntry) {
        switch (timeEntry.getStatus()) {
            case NEW:
                return "time-entry-new";
            case APPROVED:
                return "time-entry-approved";
            case REJECTED:
                return "time-entry-rejected";
            case CLOSED:
                return "time-entry-closed";
            default:
                return null;
        }
    }

    public static String getProjectStatusStyle(Project project) {
        switch (project.getStatus()) {
            case OPEN:
                return "project-open";
            case CLOSED:
                return "project-closed";
            default:
                return null;
        }
    }

    public static String createStringFromMultipleEntities(Set<Entity> entities) {
        StringBuilder sb = new StringBuilder();

        boolean first = true;
        for (Entity entity : entities) {
            if (!first) {
                sb.append(", ");
            }

            first = false;

            sb.append(metadata.getTools().getInstanceName(entity));
        }

        return sb.toString();
    }

    public static String getColumnCaption(String columnId, Date date) {
        String caption = messages.getMessage(WeeklyReportEntry.class, "WeeklyReportEntry." + columnId);
        String format = COMMON_DAY_CAPTION_STYLE;

        if (workdaysTools.isHoliday(date) || workdaysTools.isWeekend(date)) {
            format = String.format(HOLIDAY_CAPTION_STYLE, format);
        }
        if (DateUtils.isSameDay(timeSource.currentTimestamp(), date)) {
            format = String.format(TODAY_CAPTION_STYLE, format);
        }
        return String.format(format, caption, DateUtils.toCalendar(date).get(Calendar.DAY_OF_MONTH));
    }

    public static String getTimeEntryStatusStyleBg(List<TimeEntry> timeEntries) {
        if (timeEntries.isEmpty()) {
            return null;
        }
        TimeEntryStatus status = timeEntries.get(0).getStatus();
        for (TimeEntry timeEntry : timeEntries) {
            if (!status.equals(timeEntry.getStatus())) {
                return null;
            }
        }
        String style = getTimeEntryStatusStyle(timeEntries.get(0));
        return style != null ? style + "-bg" : null;
    }

    public static String getCacheKeyForEntity(Entity entity, String column) {
        return String.format("%s.%s", entity.getId(), column);
    }

    public static LinkButton createCaptionlessLinkButton(String icon, String description, Action action) {
        LinkButton linkButton = uiComponents.create(LinkButton.class);
        linkButton.setIcon(icon);
        linkButton.setDescription(description);
        linkButton.setAlignment(Component.Alignment.MIDDLE_CENTER);
        linkButton.setAction(action);
        return linkButton;
    }

    public static abstract class CustomRemoveAction extends BaseAction {

        protected Dialogs dialogs;
        protected String confirmationMessage;
        protected String confirmationTitle;

        protected CustomRemoveAction(String id, Dialogs dialogs) {
            super(id);
            this.dialogs = dialogs;
        }

        @Override
        public void actionPerform(Component component) {
            String messagesPackage = AppConfig.getMessagesPack();
            dialogs.createOptionDialog(Dialogs.MessageType.CONFIRMATION)
                    .withCaption(getConfirmationTitle(messagesPackage))
                    .withMessage(getConfirmationMessage(messagesPackage))
                    .withActions(
                            new DialogAction(DialogAction.Type.OK) {
                                @Override
                                public void actionPerform(Component component) {
                                    doRemove();
                                }
                            },
                            new DialogAction(DialogAction.Type.CANCEL)
                    )
                    .show();
        }

        @Override
        public String getCaption() {
            return null;
        }

        protected abstract void doRemove();

        protected String getConfirmationMessage(String messagesPackage) {
            if (confirmationMessage != null)
                return confirmationMessage;
            else
                return messages.getMessage(messagesPackage, "dialogs.Confirmation.Remove");
        }

        protected String getConfirmationTitle(String messagesPackage) {
            if (confirmationTitle != null)
                return confirmationTitle;
            else
                return messages.getMessage(messagesPackage, "dialogs.Confirmation");
        }
    }
}


Question: import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.haulmont.cuba.core.global.CommitContext;
import com.haulmont.cuba.core.global.DataManager;
import com.haulmont.cuba.core.global.TimeSource;
import com.haulmont.cuba.core.global.View;
import com.haulmont.cuba.gui.Dialogs;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.components.actions.BaseAction;
import com.haulmont.cuba.gui.model.CollectionContainer;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.screen.*;
import com.haulmont.cuba.security.entity.Group;
import com.haulmont.cuba.security.global.UserSession;
import com.haulmont.timesheets.entity.Project;
import com.haulmont.timesheets.entity.Task;
import com.haulmont.timesheets.entity.TimeEntry;
import com.haulmont.timesheets.entity.TimeEntryStatus;
import com.haulmont.timesheets.global.WorkTimeConfigBean;
import com.haulmont.timesheets.global.WorkdaysTools;
import com.haulmont.timesheets.gui.timeentry.AllTimeEntries;
import com.haulmont.timesheets.gui.util.ScreensHelper;
import com.haulmont.timesheets.gui.util.TimeEntryOvertimeAggregation;
import com.haulmont.timesheets.service.ProjectsService;
import org.apache.commons.lang3.time.DateUtils;
import javax.inject.Inject;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.*;
/*
 * Copyright (c) 2016 Haulmont
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.haulmont.timesheets.gui.approve;



/**
 * @author gorelov
 */
@UiController("ts$TimeEntry.all-approve")
@UiDescriptor("timeentry-all-approve.xml")
public class BulkTimeEntriesApprove extends AllTimeEntries {
    @Inject
    protected UserSession userSession;
    @Inject
    protected ProjectsService projectsService;
    @Inject
    protected MessageBundle messageBundle;
    @Inject
    protected Dialogs dialogs;
    @Inject
    protected WorkdaysTools workdaysTools;
    @Inject
    protected DataManager dataManager;
    @Inject
    protected TimeSource timeSource;
    @Inject
    protected WorkTimeConfigBean workTimeConfigBean;
    @Inject
    protected PopupButton approve;
    @Inject
    protected PopupButton reject;
    @Inject
    protected LookupField user;
    @Inject
    protected LookupField status;
    @Inject
    protected CollectionLoader<Group> groupsDl;
    @Inject
    protected CollectionLoader<Project> projectsDl;
    @Inject
    protected LookupField<Group> group;
    @Inject
    protected LookupField<Project> project;
    @Inject
    protected CollectionLoader<Task> tasksDl;
    @Inject
    protected LookupField<Task> task;
    @Inject
    protected DateField<Date> dateFrom;
    @Inject
    protected DateField<Date> dateTo;

    @Subscribe("timeEntriesTable.refresh")
    protected void onTimeEntriesTableRefresh(Action.ActionPerformedEvent event) {
        refresh();
    }

    @Subscribe(id = "projectsDc", target = Target.DATA_CONTAINER)
    private void onProjectsDcItemChange(InstanceContainer.ItemChangeEvent<Project> e) {
        tasksDl.setParameter("project", e.getItem());
        tasksDl.load();
    }

    @Subscribe
    @Override
    public void onInit(InitEvent event) {
        super.onInit(event);

        timeEntriesTable.getColumn("overtime").setAggregation(
                ScreensHelper.createAggregationInfo(
                        projectsService.getEntityMetaPropertyPath(TimeEntry.class, "overtime"),
Answer:                         new TimeEntryOvertimeAggregation()));