/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.web.workreports; import static org.libreplan.web.I18nHelper._; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.joda.time.LocalDate; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.common.exceptions.ValidationException.InvalidValue; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; import org.libreplan.business.labels.entities.Label; import org.libreplan.business.labels.entities.LabelType; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workreports.entities.HoursManagementEnum; import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReportLabelTypeAssignment; import org.libreplan.business.workreports.entities.WorkReportLine; import org.libreplan.business.workreports.entities.WorkReportType; import org.libreplan.business.workreports.valueobjects.DescriptionField; import org.libreplan.business.workreports.valueobjects.DescriptionValue; import org.libreplan.web.common.ConstraintChecker; import org.libreplan.web.common.IMessagesForUser; import org.libreplan.web.common.Level; import org.libreplan.web.common.MessagesForUser; import org.libreplan.web.common.OnlyOneVisible; import org.libreplan.web.common.Util; import org.libreplan.web.common.components.Autocomplete; import org.libreplan.web.common.components.NewDataSortableColumn; import org.libreplan.web.common.components.NewDataSortableGrid; import org.libreplan.web.common.components.bandboxsearch.BandboxSearch; import org.libreplan.web.common.entrypoints.IURLHandlerRegistry; import org.libreplan.web.users.dashboard.IPersonalTimesheetController; import org.zkoss.ganttz.IPredicate; import org.zkoss.ganttz.util.ComponentsFinder; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.CheckEvent; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.SelectEvent; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zkplus.spring.SpringUtil; import org.zkoss.zul.Button; import org.zkoss.zul.Checkbox; import org.zkoss.zul.Column; import org.zkoss.zul.Columns; import org.zkoss.zul.Comboitem; import org.zkoss.zul.Constraint; import org.zkoss.zul.Datebox; import org.zkoss.zul.Grid; import org.zkoss.zul.ListModel; import org.zkoss.zul.Listbox; import org.zkoss.zul.Listitem; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Popup; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Textbox; import org.zkoss.zul.Timebox; import org.zkoss.zul.Window; /** * Controller for CRUD actions over a {@link WorkReport}. * * @author Diego Pino García <dpino@igalia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Vova Perebykivskyi <vova@libreplan-enterpsire.com> * @author Bogdan Bodnarjuk <bogdan@libreplan-enterpsire.com> */ public class WorkReportCRUDController extends GenericForwardComposer<Component> implements IWorkReportCRUDControllerEntryPoints { private static final String MOLD = "paging"; private static final int PAGING = 10; private boolean cameBackList = false; private Window createWindow; private Window listWindow; private IWorkReportModel workReportModel; private IURLHandlerRegistry URLHandlerRegistry; private OnlyOneVisible visibility; private IMessagesForUser messagesForUser; private Component messagesContainer; private IWorkReportTypeCRUDControllerEntryPoints workReportTypeCRUD; private WorkReportListRenderer workReportListRenderer = new WorkReportListRenderer(); private OrderedFieldsAndLabelsRowRenderer orderedFieldsAndLabelsRowRenderer = new OrderedFieldsAndLabelsRowRenderer(); private NewDataSortableGrid listWorkReportLines; private Grid headingFieldsAndLabels; private Autocomplete autocompleteResource; private BandboxSearch bandboxSelectOrderElementInHead; private ListModel allHoursType; private static final String ITEM = "item"; private static final int EXTRA_FIELD_MIN_WIDTH = 70; private static final int EXTRA_FIELD_MAX_WIDTH = 150; private static final int EXTRA_FIELD_PX_PER_CHAR = 5; private transient IPredicate predicate; private Grid listing; private Listbox listType; private Listbox listTypeToAssign; private Datebox filterStartDate; private Datebox filterFinishDate; private IPersonalTimesheetController personalTimesheetController; private Popup personalTimesheetsPopup; private Datebox personalTimesheetsDatebox; private BandboxSearch personalTimesheetsBandboxSearch; private WorkReportType firstType; private static final String ASCENDING = "ascending"; public WorkReportCRUDController() { } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); listWorkReportLines = (NewDataSortableGrid) createWindow.getFellowIfAny("listWorkReportLines"); messagesForUser = new MessagesForUser(messagesContainer); showMessageIfPersonalTimesheetWasSaved(); injectsObjects(); comp.setAttribute("controller", this); goToList(); if ( listType != null ) { // listType is null in reports -> work report lines listType.setSelectedIndex(0); } initializeHoursType(); URLHandlerRegistry.getRedirectorFor(IWorkReportCRUDControllerEntryPoints.class).register(this, page); } private void injectsObjects() { workReportModel = (IWorkReportModel) SpringUtil.getBean("workReportModel"); URLHandlerRegistry = (IURLHandlerRegistry) SpringUtil.getBean("URLHandlerRegistry"); workReportTypeCRUD = (IWorkReportTypeCRUDControllerEntryPoints) SpringUtil.getBean("workReportTypeCRUD"); personalTimesheetController = (IPersonalTimesheetController) SpringUtil.getBean("personalTimesheetController"); } private void showMessageIfPersonalTimesheetWasSaved() { String timesheetSave = Executions.getCurrent().getParameter("timesheet_saved"); if ( !StringUtils.isBlank(timesheetSave) ) { messagesForUser.showMessage(Level.INFO, _("Personal timesheet saved")); } } private void initializeHoursType() { allHoursType = new SimpleListModel<>(workReportModel.getAllHoursType()); } /** * Show confirm window for deleting {@link WorkReport} * * @param workReportDTO */ public void showConfirmDelete(WorkReportDTO workReportDTO) { WorkReport workReport = workReportDTO.getWorkReport(); final String workReportName = formatWorkReportName(workReport); int status = Messagebox.show( _("Confirm deleting {0}. Are you sure?", workReportName), "Delete", Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION); if ( Messagebox.OK == status ) { workReportModel.remove(workReport); messagesForUser.showMessage(Level.INFO, _("Timesheet removed successfully")); loadComponentslist(listWindow); Util.reloadBindings(listWindow); } } private String formatWorkReportName(WorkReport workReport) { return workReport.getWorkReportType().getName(); } public List<WorkReportDTO> getWorkReportDTOs() { return workReportModel.getWorkReportDTOs(); } private OnlyOneVisible getVisibility() { return (visibility == null) ? new OnlyOneVisible(createWindow, listWindow) : visibility; } public void saveAndExit() { if ( save() ) { goToList(); } } public void saveAndContinue() { if ( save() ) { goToEditForm(getWorkReport()); } } public boolean save() { ConstraintChecker.isValid(createWindow); workReportModel.generateWorkReportLinesIfIsNecessary(); try { workReportModel.confirmSave(); messagesForUser.showMessage(Level.INFO, _("Timesheet saved")); return true; } catch (ValidationException e) { showInvalidValues(e); } catch (Exception e) { if ( !showInvalidProperty() ) { throw new RuntimeException(e); } } return false; } /** * Shows invalid values for {@link WorkReport} and {@link WorkReportLine} entities. * * @param e */ private void showInvalidValues(ValidationException e) { for (InvalidValue invalidValue : e.getInvalidValues()) { Object value = invalidValue.getRootBean(); if ( value instanceof WorkReport && validateWorkReport() ) messagesForUser.showInvalidValues(e); if ( value instanceof WorkReportLine ) { WorkReportLine workReportLine = (WorkReportLine) invalidValue.getRootBean(); Row row = ComponentsFinder.findRowByValue(listWorkReportLines, workReportLine); if ( row == null ) { messagesForUser.showInvalidValues(e); } else { validateWorkReportLine(row, workReportLine); } } } } private boolean showInvalidProperty() { WorkReport workReport = getWorkReport(); if ( workReport != null ) { if ( !validateWorkReport() ) { return true; } for (WorkReportLine each : workReport.getWorkReportLines()) { if ( !validateWorkReportLine(each) ) { return true; } } } return false; } /** * Validates {@link WorkReport} data constraints. * * @param invalidValue */ private boolean validateWorkReport() { if ( !getWorkReport().isDateMustBeNotNullIfIsSharedByLinesConstraint() ) { Datebox datebox = (Datebox) createWindow.getFellowIfAny("date"); showInvalidMessage(datebox, _("cannot be empty")); return false; } if ( !getWorkReport().isResourceMustBeNotNullIfIsSharedByLinesConstraint() ) { showInvalidMessage(autocompleteResource, _("cannot be empty")); return false; } if ( !getWorkReport().isOrderElementMustBeNotNullIfIsSharedByLinesConstraint() ) { showInvalidMessage(bandboxSelectOrderElementInHead, _("cannot be empty")); return false; } return true; } private boolean validateWorkReportLine(WorkReportLine workReportLine) { Row row = ComponentsFinder.findRowByValue(listWorkReportLines, workReportLine); return row != null && validateWorkReportLine(row, workReportLine); } /** * Validates {@link WorkReportLine} data constraints. * * @param invalidValue */ @SuppressWarnings("unchecked") private boolean validateWorkReportLine(Row row, WorkReportLine workReportLine) { if ( getWorkReportType().getDateIsSharedByLines() ) { if ( !validateWorkReport() ) { return false; } } else if ( workReportLine.getDate() == null) { Datebox date = getDateboxDate(row); if ( date != null ) { String message = _("cannot be empty"); showInvalidMessage(date, message); } return false; } if ( getWorkReportType().getResourceIsSharedInLines() ) { if ( !validateWorkReport() ) { return false; } } else if ( workReportLine.getResource() == null ) { Autocomplete autoResource = getTextboxResource(row); if ( autoResource != null ) { String message = _("cannot be empty"); showInvalidMessage(autoResource, message); } return false; } if ( getWorkReportType().getOrderElementIsSharedInLines() ) { if ( !validateWorkReport() ) { return false; } } else if ( workReportLine.getOrderElement() == null ) { BandboxSearch bandboxOrder = getTextboxOrder(row); if ( bandboxOrder != null ) { String message = _("cannot be empty"); bandboxOrder.clear(); showInvalidMessage(bandboxOrder, message); } return false; } if ( !workReportLine.isClockStartMustBeNotNullIfIsCalculatedByClockConstraint() ) { Timebox timeStart = getTimeboxStart(row); if ( timeStart != null ) { String message = _("cannot be empty"); showInvalidMessage(timeStart, message); } return false; } if ( !workReportLine.isClockFinishMustBeNotNullIfIsCalculatedByClockConstraint() ) { Timebox timeFinish = getTimeboxFinish(row); if ( timeFinish != null ) { String message = _("cannot be empty"); showInvalidMessage(timeFinish, message); } return false; } if ( workReportLine.getEffort() == null ) { Textbox effort = getEffort(row); if ( effort == null ) { String message = _("cannot be empty"); showInvalidMessage(null, message); } if ( effort != null && EffortDuration.zero().compareTo(EffortDuration.parseFromFormattedString(effort.getValue())) <= 0 ) { String message = _("Effort must be greater than zero"); showInvalidMessage(effort, message); } return false; } if ( !workReportLine.isHoursCalculatedByClockConstraint() ) { Textbox effort = getEffort(row); if ( effort != null ) { String message = _("effort is not properly calculated based on clock"); showInvalidMessage(effort, message); } return false; } if ( workReportLine.getTypeOfWorkHours() == null ) { // Locate TextboxOrder Listbox autoTypeOfHours = getTypeOfHours(row); if ( autoTypeOfHours != null ) { String message = autoTypeOfHours.getItems().isEmpty() ? _("Hours types are empty. Please, create some hours types before proceeding") : _("cannot be empty"); showInvalidMessage(autoTypeOfHours, message); } return false; } if ( (!getWorkReport().isCodeAutogenerated()) && (workReportLine.getCode() == null || workReportLine.getCode().isEmpty()) ) { // Locate TextboxCode Textbox txtCode = getCode(row); if ( txtCode != null ) { String message = _("cannot be empty."); showInvalidMessage(txtCode, message); } return false; } if ( !workReportLine.isOrderElementFinishedInAnotherWorkReportConstraint() ) { Checkbox checkboxFinished = getFinished(row); if ( checkboxFinished != null ) { String message = _("task is already marked as finished in another timesheet"); showInvalidMessage(checkboxFinished, message); } return false; } return true; } private void showInvalidMessage(Component comp, String message) { throw new WrongValueException(comp, message); } /** * Locates {@link Timebox} time finish in {@link Row}. * * @param row * @return */ private Timebox getTimeboxFinish(Row row) { try { int position = row.getChildren().size() - 6; return (Timebox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Timebox} time start in {@link Row}. * * @param row * @return */ private Timebox getTimeboxStart(Row row) { try { int position = row.getChildren().size() - 7; return (Timebox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Autocomplete} type of work hours in {@link Row}. * * @param row * @return */ private Listbox getTypeOfHours(Row row) { try { int position = row.getChildren().size() - 4; return (Listbox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Checkbox} finished in {@link Row}. * * @param row * @return */ private Checkbox getFinished(Row row) { try { int position = row.getChildren().size() - 3; return (Checkbox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Texbox} code in {@link Row}. * * @param row * @return */ private Textbox getCode(Row row) { try { int position = row.getChildren().size() - 2; return (Textbox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Textbox} effort in {@link Row}. * * @param row * @return */ private Textbox getEffort(Row row) { try { int position = row.getChildren().size() - 5; return (Textbox) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Datebox} date in {@link Row}. * * @param row * @return */ private Datebox getDateboxDate(Row row) { try { return (Datebox) row.getChildren().get(0); } catch (Exception e) { return null; } } /** * Locates {@link Textbox} Resource in {@link Row}. * * @param row * @return */ private Autocomplete getTextboxResource(Row row) { int position = 0; if ( !getWorkReportType().getDateIsSharedByLines() ) { position++; } try { return (Autocomplete) row.getChildren().get(position); } catch (Exception e) { return null; } } /** * Locates {@link Textbox} Order in {@link Row}. * * @param row * @return */ private BandboxSearch getTextboxOrder(Row row) { int position = 0; if ( !getWorkReportType().getDateIsSharedByLines() ) { position++; } if ( !getWorkReportType().getResourceIsSharedInLines() ) { position++; } try { return (BandboxSearch) row.getChildren().get(position); } catch (Exception e) { return null; } } @Override public void goToList() { getVisibility().showOnly(listWindow); loadComponentslist(listWindow); Util.reloadBindings(listWindow); } public void cancel() { if ( cameBackList || workReportModel.isEditing() ) { goToList(); } else { workReportTypeCRUD.goToList(); } } @Override public void goToCreateForm(WorkReportType workReportType) { if ( workReportType.isPersonalTimesheetsType() ) { personalTimesheetsPopup.open(listTypeToAssign); } else { cameBackList = false; workReportModel.initCreate(workReportType); prepareWorkReportList(); createWindow.setTitle(_("Create Timesheet")); getVisibility().showOnly(createWindow); loadComponents(createWindow); Util.reloadBindings(createWindow); } } public void goToEditForm(WorkReportDTO workReportDTO) { goToEditForm(workReportDTO.getWorkReport()); } @Override public void goToEditForm(WorkReport workReport) { if ( workReportModel.isPersonalTimesheet(workReport) ) { goToEditPersonalTimeSheet(workReport); } else { workReportModel.initEdit(workReport); createWindow.setTitle(_("Edit Timesheet")); loadComponents(createWindow); prepareWorkReportList(); getVisibility().showOnly(createWindow); Util.reloadBindings(createWindow); } } private void goToEditPersonalTimeSheet(WorkReport workReport) { workReportModel.initEdit(workReport); Date date = workReportModel.getFirstWorkReportLine().getDate(); Resource resource = workReport.getResource(); personalTimesheetController.goToCreateOrEditFormForResource(LocalDate.fromDateFields(date), resource); } private void loadComponents(Component window) { listWorkReportLines = (NewDataSortableGrid) window.getFellow("listWorkReportLines"); headingFieldsAndLabels = (Grid) window.getFellow("headingFieldsAndLabels"); autocompleteResource = (Autocomplete) window.getFellow("autocompleteResource"); bandboxSelectOrderElementInHead = (BandboxSearch) window.getFellow("bandboxSelectOrderElementInHead"); bandboxSelectOrderElementInHead.setListboxWidth("750px"); bandboxSelectOrderElementInHead.setListboxEventListener( Events.ON_SELECT, event -> { Listitem selectedItem = (Listitem) ((SelectEvent) event).getSelectedItems().iterator().next(); OrderElement orderElement = selectedItem.getValue(); getWorkReport().setOrderElement(orderElement); }); bandboxSelectOrderElementInHead.setListboxEventListener( Events.ON_OK, event -> { Listitem selectedItem = bandboxSelectOrderElementInHead.getSelectedItem(); if ((selectedItem != null) && (getWorkReport() != null)) { getWorkReport().setOrderElement(selectedItem.getValue()); } bandboxSelectOrderElementInHead.close(); }); } private void loadComponentslist(Component window) { // Components work report list listing = (Grid) window.getFellow("listing"); listType = (Listbox) window.getFellow("listType"); listTypeToAssign = (Listbox) window.getFellow("listTypeToAssign"); filterStartDate = (Datebox) window.getFellow("filterStartDate"); filterFinishDate = (Datebox) window.getFellow("filterFinishDate"); personalTimesheetsPopup = (Popup) window.getFellow("personalTimesheetsPopup"); personalTimesheetsDatebox = (Datebox) window.getFellow("personalTimesheetsDatebox"); personalTimesheetsBandboxSearch = (BandboxSearch) window.getFellow("personalTimesheetsBandboxSearch"); clearFilterDates(); } /** * {@link WorkReportLine} list is finally constructed dynamically. * * It seems there are some problems when a list of data is rendered, * modified (the data model changes), and it's rendered again. * Deleting previous settings and re-establishing the settings again each time the * list is rendered, solve those problems. */ private void prepareWorkReportList() { /* * The only way to clean the listhead, is to clean all its attributes * and children The paging component cannot be removed manually. * It is removed automatically when changing the mold. */ listWorkReportLines.setMold(null); listWorkReportLines.getChildren().clear(); // Set mold and pagesize listWorkReportLines.setMold(MOLD); listWorkReportLines.setPageSize(PAGING); appendColumns(listWorkReportLines); listWorkReportLines.setSortedColumn((NewDataSortableColumn) listWorkReportLines.getColumns().getFirstChild()); listWorkReportLines.setModel(new SimpleListModel<>(getWorkReportLines().toArray())); } /** * Appends list headers to {@link WorkReportLine} list. * * @param grid */ private void appendColumns(Grid grid) { Columns columns = grid.getColumns(); // Create listhead first time is rendered if ( columns == null ) { columns = new Columns(); } // Delete all headers columns.getChildren().clear(); columns.setSizable(true); // Add static headers if ( getWorkReport() != null ) { if ( !getWorkReport().getWorkReportType().getDateIsSharedByLines() ) { NewDataSortableColumn columnDate = new NewDataSortableColumn(); columnDate.setLabel(_("Date")); columnDate.setSclass("date-column"); columnDate.setHflex("1"); Util.setSort(columnDate, "auto=(date)"); columnDate.setSortDirection(ASCENDING); columnDate.addEventListener("onSort", event -> sortWorkReportLines()); columns.appendChild(columnDate); } if ( !getWorkReport().getWorkReportType().getResourceIsSharedInLines() ) { NewDataSortableColumn columnResource = new NewDataSortableColumn(); columnResource.setLabel(_("Resource")); columnResource.setHflex("1"); columnResource.setSclass("resource-column"); columns.appendChild(columnResource); } if ( !getWorkReport().getWorkReportType().getOrderElementIsSharedInLines() ) { NewDataSortableColumn columnCode = new NewDataSortableColumn(); columnCode.setLabel(_("Task")); columnCode.setSclass("order-code-column"); columnCode.setHflex("1"); columns.appendChild(columnCode); } for (Object fieldOrLabel : workReportModel.getFieldsAndLabelsLineByDefault()) { String columnName; int width = EXTRA_FIELD_MIN_WIDTH; if ( fieldOrLabel instanceof DescriptionField ) { columnName = ((DescriptionField) fieldOrLabel).getFieldName(); width = Math.max( ((DescriptionField) fieldOrLabel).getLength() * EXTRA_FIELD_PX_PER_CHAR, EXTRA_FIELD_MIN_WIDTH); width = Math.min(width, EXTRA_FIELD_MAX_WIDTH); } else { columnName = ((WorkReportLabelTypeAssignment) fieldOrLabel).getLabelType().getName(); } NewDataSortableColumn columnFieldOrLabel = new NewDataSortableColumn(); columnFieldOrLabel.setLabel(_(columnName)); columnFieldOrLabel.setSclass("columnFieldOrLabel"); columnFieldOrLabel.setWidth(width + "px"); columns.appendChild(columnFieldOrLabel); } if ( !getWorkReport().getWorkReportType().getHoursManagement() .equals(HoursManagementEnum.NUMBER_OF_HOURS) ) { NewDataSortableColumn columnHourStart = new NewDataSortableColumn(); columnHourStart.setLabel(_("Start hour")); columnHourStart.setSclass("column-hour-start"); columnHourStart.setHflex("min"); columns.appendChild(columnHourStart); NewDataSortableColumn columnHourFinish = new NewDataSortableColumn(); columnHourFinish.setLabel(_("Finish Hour")); columnHourStart.setSclass("column-hour-finish"); columns.appendChild(columnHourFinish); } } NewDataSortableColumn columnNumHours = new NewDataSortableColumn(); columnNumHours.setLabel(_("Hours")); columnNumHours.setSclass("hours-column"); columns.appendChild(columnNumHours); NewDataSortableColumn columnHoursType = new NewDataSortableColumn(); columnHoursType.setLabel(_("Hours type")); columnHoursType.setSclass("hours-type-column"); columns.appendChild(columnHoursType); NewDataSortableColumn columnFinsihed = new NewDataSortableColumn(); columnFinsihed.setLabel(_("Done")); columnFinsihed.setSclass("finished-column"); columnFinsihed.setTooltiptext(_("Task finished")); NewDataSortableColumn columnCode = new NewDataSortableColumn(); columns.appendChild(columnFinsihed); columnCode.setLabel(_("Code")); columnCode.setSclass("code-column"); columnCode.setHflex("1"); columns.appendChild(columnCode); NewDataSortableColumn columnOperations = new NewDataSortableColumn(); columnOperations.setLabel(_("Op.")); columnOperations.setSclass("operations-column"); columnOperations.setTooltiptext(_("Operations")); columnOperations.setHflex("min"); columns.appendChild(columnOperations); columns.setParent(grid); } private WorkReportType getWorkReportType() { return getWorkReport().getWorkReportType(); } public WorkReport getWorkReport() { return workReportModel.getWorkReport(); } /** * Adds a new {@link WorkReportLine} to the list of rows. */ public void addWorkReportLine() { workReportModel.addWorkReportLine(); reloadWorkReportLines(); } private void removeWorkReportLine(WorkReportLine workReportLine) { workReportModel.removeWorkReportLine(workReportLine); reloadWorkReportLines(); } public List<WorkReportLine> getWorkReportLines() { return workReportModel.getWorkReportLines(); } protected void setClock(WorkReportLine line, Timebox timeStart, Timebox timeFinish) { line.setClockStart(timeStart.getValue()); line.setClockFinish(timeFinish.getValue()); } public void checkCannotBeHigher(Timebox starting, Timebox ending) { starting.clearErrorMessage(true); ending.clearErrorMessage(true); final Date startingDate = starting.getValue(); final Date endingDate = ending.getValue(); if ( endingDate == null || startingDate == null || startingDate.compareTo(endingDate) > 0 ) { throw new WrongValueException(starting, _("Cannot be higher than finish hour")); } } public void confirmRemove(WorkReportLine workReportLine) { int status = Messagebox.show( _("Confirm deleting {0}. Are you sure?", getWorkReportLineName(workReportLine)), _("Delete"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION); if ( Messagebox.OK == status ) { removeWorkReportLine(workReportLine); } } private String getWorkReportLineName(WorkReportLine workReportLine) { final Resource resource = workReportLine.getResource(); final OrderElement orderElement = workReportLine.getOrderElement(); if ( resource == null || orderElement == null ) { return ITEM; } return resource.getShortDescription() + " - " + orderElement.getCode(); } public WorkReportListRenderer getRenderer() { return workReportListRenderer; } public class WorkReportListRenderer implements RowRenderer { /** * RowRenderer for a @{WorkReportLine} element. * * @author Diego Pino García <dpino@igalia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> */ @Override public void render(Row row, Object o, int i) throws Exception { WorkReportLine workReportLine = (WorkReportLine) o; row.setValue(workReportLine); // Create TextBoxes if (!getWorkReport().getWorkReportType().getDateIsSharedByLines()) { appendDateInLines(row); } if (!getWorkReport().getWorkReportType().getResourceIsSharedInLines()) { appendResourceInLines(row); } if (!getWorkReport().getWorkReportType().getOrderElementIsSharedInLines()) { appendOrderElementInLines(row); } // Create the fields and labels appendFieldsAndLabelsInLines(row); NewDataSortableGrid grid = (NewDataSortableGrid) row.getParent().getParent(); NewDataSortableColumn priorityColumn = (NewDataSortableColumn) grid.getChildren().get(1).getChildren().get(2); priorityColumn.setWidth("110px"); if (!getWorkReport().getWorkReportType().getHoursManagement().equals(HoursManagementEnum.NUMBER_OF_HOURS)) { appendHoursStartAndFinish(row); } appendEffortDuration(row); appendHoursType(row); appendFinished(row); appendCode(row); appendDeleteButton(row); } private void setOrderElementInWRL(Listitem selectedItem, WorkReportLine line) { OrderElement orderElement = selectedItem.getValue(); line.setOrderElement(orderElement); } private void appendFinished(final Row row) { final WorkReportLine line = row.getValue(); Checkbox finished = Util.bind( new Checkbox(), () -> line.isFinished(), value -> line.setFinished(BooleanUtils.isTrue(value)) ); if ( !line.isFinished() && workReportModel.isFinished(line.getOrderElement()) ) { finished.setDisabled(true); } row.appendChild(finished); } private void appendAutocompleteLabelsByTypeInLine(Row row, final Label currentLabel) { final LabelType labelType = currentLabel.getType(); final WorkReportLine line = row.getValue(); final Autocomplete comboLabels = createAutocompleteLabels(labelType, currentLabel); comboLabels.setParent(row); comboLabels.addEventListener(Events.ON_CHANGE, event -> { if (comboLabels.getSelectedItem() != null) { Label newLabel = comboLabels.getSelectedItem().getValue(); workReportModel.changeLabelInWorkReportLine(currentLabel, newLabel, line); } reloadWorkReportLines(); }); } private void appendDateInLines(final Row row) { final Datebox date = new Datebox(); final WorkReportLine line = row.getValue(); Util.bind( date, () -> { if (line != null) { return line.getDate(); } return null; }, value -> { if (line != null) { line.setDate(value); } }); row.appendChild(date); } /** * Append a Autocomplete @{link Resource} to row. * * @param row */ private void appendResourceInLines(final Row row) { final Autocomplete autocomplete = new Autocomplete(); autocomplete.setWidth("200px"); autocomplete.setAutodrop(true); autocomplete.applyProperties(); autocomplete.setFinder("ResourceFinder"); // Getter, show worker selected if ( getResource(row) != null ) { autocomplete.setSelectedItem(getResource(row)); } autocomplete.addEventListener("onChange", event -> changeResourceInLines(autocomplete, row)); row.appendChild(autocomplete); } private void setHoursType(WorkReportLine workReportLine, Listitem item) { TypeOfWorkHours value = item != null ? (TypeOfWorkHours) item.getValue() : null; workReportLine.setTypeOfWorkHours(value); if (value == null && item != null) { throw new WrongValueException(item.getParent(), _("Please, select an item")); } } /** * Append a {@link Textbox} effort to {@link Row}. * * @param row */ private void appendEffortDuration(Row row) { WorkReportLine workReportLine = row.getValue(); Textbox effort = new Textbox(); effort.setConstraint((comp, value) -> { if ( !Pattern.matches("(\\d+)(\\s*:\\s*\\d+\\s*)*", (String) value)) throw new WrongValueException(comp, _("Please, enter a valid effort")); }); bindEffort(effort, workReportLine); if ( getWorkReportType().getHoursManagement().equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK) ) { effort.setDisabled(true); } row.appendChild(effort); } private void appendFieldsAndLabelsInLines(final Row row) { final WorkReportLine line = row.getValue(); for(Object fieldOrLabel : getFieldsAndLabelsLine(line)) { if ( fieldOrLabel instanceof DescriptionValue ) { appendNewTextbox(row, (DescriptionValue) fieldOrLabel); } else if ( fieldOrLabel instanceof Label ) { appendAutocompleteLabelsByTypeInLine(row, (Label) fieldOrLabel); } } } /** * Append Selectbox of @{link TypeOfWorkHours} to row. * * @param row */ private void appendHoursType(final Row row) { final WorkReportLine workReportLine = row.getValue(); final Listbox lbHoursType = new Listbox(); lbHoursType.setMold("select"); lbHoursType.setModel(allHoursType); lbHoursType.renderAll(); lbHoursType.applyProperties(); if ( lbHoursType.getItems().isEmpty() ) { row.appendChild(lbHoursType); return; } // First time is rendered, select first item TypeOfWorkHours type = workReportLine.getTypeOfWorkHours(); if ( workReportLine.isNewObject() && type == null) { Listitem item = lbHoursType.getItemAtIndex(0); item.setSelected(true); setHoursType(workReportLine, item); } else { // If workReportLine has a type, select item with that type Listitem item = ComponentsFinder.findItemByValue(lbHoursType, type); if ( item != null ) { lbHoursType.selectItem(item); } } lbHoursType.addEventListener(Events.ON_SELECT, event -> { Listitem item = lbHoursType.getSelectedItem(); if ( item != null ) { setHoursType(row.getValue(), item); } }); row.appendChild(lbHoursType); } /** * Append a delete {@link Button} to {@link Row}. * * @param row */ private void appendDeleteButton(final Row row) { Button delete = new Button("", "/common/img/ico_borrar1.png"); delete.setHoverImage("/common/img/ico_borrar.png"); delete.setSclass("icono"); delete.setTooltiptext(_("Delete")); delete.addEventListener(Events.ON_CLICK, event -> confirmRemove(row.getValue())); row.appendChild(delete); } /** * Binds Textbox effort to a {@link WorkReportLine} numHours. * * @param box * @param workReportLine */ private void bindEffort(final Textbox box, final WorkReportLine workReportLine) { Util.bind( box, () -> workReportLine.getEffort() != null ? workReportLine.getEffort().toFormattedString() : EffortDuration.zero().toFormattedString(), value -> workReportLine.setEffort(EffortDuration.parseFromFormattedString(value)) ); } private void appendCode(final Row row) { final WorkReportLine line = row.getValue(); final Textbox code = new Textbox(); code.setDisabled(getWorkReport().isCodeAutogenerated()); code.applyProperties(); if ( line.getCode() != null ) { code.setValue(line.getCode()); } code.addEventListener("onChange", event -> { final WorkReportLine line1 = row.getValue(); line1.setCode(code.getValue()); }); row.appendChild(code); } private Timebox getNewTimebox() { final Timebox timeStart = new Timebox(); timeStart.setWidth("60px"); timeStart.setFormat("short"); timeStart.setButtonVisible(true); return timeStart; } private void updateEffort(final Row row) { WorkReportLine line = row.getValue(); Textbox effort = getEffort(row); if ( effort != null && line.getEffort() != null ) { effort.setValue(line.getEffort().toFormattedString()); effort.invalidate(); } } private void appendHoursStartAndFinish(final Row row) { final WorkReportLine line = row.getValue(); final Timebox timeStart = getNewTimebox(); final Timebox timeFinish = getNewTimebox(); row.appendChild(timeStart); row.appendChild(timeFinish); Util.bind( timeStart, () -> { if ( (line != null) && (line.getClockStart() != null) ) { return line.getClockStart().toDateTimeToday().toDate(); } return null; }, value -> { if ( line != null ) { checkCannotBeHigher(timeStart, timeFinish); setClock(line, timeStart, timeFinish); updateEffort(row); } }); Util.bind( timeFinish, () -> { if ( (line != null) && (line.getClockStart() != null) ) { return line.getClockFinish().toDateTimeToday().toDate(); } return null; }, value -> { if ( line != null ) { checkCannotBeHigher(timeStart, timeFinish); setClock(line, timeStart, timeFinish); updateEffort(row); } }); } /** * Append a Textbox @{link Order} to row. * * @param row */ private void appendOrderElementInLines(Row row) { final WorkReportLine workReportLine = row.getValue(); final BandboxSearch bandboxSearch = BandboxSearch.create("OrderElementBandboxFinder", getOrderElements()); bandboxSearch.setSelectedElement(workReportLine.getOrderElement()); bandboxSearch.setSclass("bandbox-workreport-task"); bandboxSearch.setListboxWidth("750px"); bandboxSearch.setListboxEventListener( Events.ON_SELECT, event -> { Listitem selectedItem = bandboxSearch.getSelectedItem(); setOrderElementInWRL(selectedItem, workReportLine); }); bandboxSearch.setListboxEventListener( Events.ON_OK, event -> { Listitem selectedItem = bandboxSearch.getSelectedItem(); setOrderElementInWRL(selectedItem, workReportLine); bandboxSearch.close(); }); row.appendChild(bandboxSearch); } private Resource getResource(Row listitem) { WorkReportLine workReportLine = listitem.getValue(); return workReportLine.getResource(); } private void changeResourceInLines(final Autocomplete autocomplete, Row row) { final WorkReportLine workReportLine = row.getValue(); final Comboitem comboitem = autocomplete.getSelectedItem(); if ( (comboitem == null) || (comboitem.getValue() == null)) { workReportLine.setResource(null); throw new WrongValueException(autocomplete, _("Please, select an item")); } else { workReportLine.setResource(comboitem.getValue()); } } } public OrderedFieldsAndLabelsRowRenderer getOrderedFieldsAndLabelsRowRenderer() { return orderedFieldsAndLabelsRowRenderer; } public class OrderedFieldsAndLabelsRowRenderer implements RowRenderer { @Override public void render(Row row, Object o, int i) throws Exception { row.setValue(o); if ( o instanceof DescriptionValue ) { appendNewLabel(row, ((DescriptionValue) o).getFieldName()); appendNewTextbox(row, (DescriptionValue) o); } else { appendNewLabel(row, ((Label) o).getType().getName()); appendAutocompleteLabelsByType(row, (Label) o); } } private void appendNewLabel(Row row, String label) { org.zkoss.zul.Label labelName = new org.zkoss.zul.Label(); labelName.setParent(row); labelName.setValue(label); } private void appendAutocompleteLabelsByType(Row row, final Label currentLabel) { final LabelType labelType = currentLabel.getType(); final Autocomplete comboLabels = createAutocompleteLabels(labelType, currentLabel); comboLabels.setParent(row); comboLabels.addEventListener(Events.ON_CHANGE, event -> { if ( comboLabels.getSelectedItem() != null ) { Label newLabel = comboLabels.getSelectedItem().getValue(); workReportModel.changeLabelInWorkReport(currentLabel, newLabel); } Util.reloadBindings(headingFieldsAndLabels); }); } } private void appendNewTextbox(Row row, final DescriptionValue descriptionValue) { Textbox textbox = new Textbox(); Integer length = workReportModel.getLength(descriptionValue); textbox.setCols(length); textbox.setParent(row); textbox.setTooltiptext(descriptionValue.getValue()); Util.bind( textbox, descriptionValue::getValue, descriptionValue::setValue); } private Autocomplete createAutocompleteLabels(LabelType labelType, Label selectedLabel) { Autocomplete comboLabels = new Autocomplete(); comboLabels.setButtonVisible(true); comboLabels.setWidth("100px"); if ( labelType != null ) { final List<Label> listLabel = getMapLabelTypes().get(labelType); for (Label label : listLabel) { Comboitem comboItem = new Comboitem(); comboItem.setValue(label); comboItem.setLabel(label.getName()); comboItem.setParent(comboLabels); if ( (selectedLabel != null) && (selectedLabel.equals(label)) ) { comboLabels.setSelectedItem(comboItem); } } } return comboLabels; } public List<Object> getFieldsAndLabelsHeading() { return workReportModel.getFieldsAndLabelsHeading(); } public List<Object> getFieldsAndLabelsLine(WorkReportLine workReportLine) { return workReportModel.getFieldsAndLabelsLine(workReportLine); } private Map<LabelType, List<Label>> getMapLabelTypes() { return workReportModel.getMapAssignedLabelTypes(); } public void changeResource(Comboitem selectedItem) { if ( selectedItem != null ) { getWorkReport().setResource(selectedItem.getValue()); } else { getWorkReport().setResource(null); } } private void reloadWorkReportLines() { this.prepareWorkReportList(); Util.reloadBindings(listWorkReportLines); } private void sortWorkReportLines() { listWorkReportLines.setModel(new SimpleListModel<>(getWorkReportLines().toArray())); } /* It should be public! */ public void sortWorkReports() { Column columnDateStart = (Column) listWindow.getFellow("columnDateStart"); if ( columnDateStart != null ) { if ( columnDateStart.getSortDirection().equals(ASCENDING)) { columnDateStart.sort(false, false); columnDateStart.setSortDirection(ASCENDING); } else if ( columnDateStart.getSortDirection().equals("descending") ) { columnDateStart.sort(true, false); columnDateStart.setSortDirection("descending"); } } } /** * It should be public! */ public List<WorkReportType> getFilterWorkReportTypes() { List<WorkReportType> result = workReportModel.getWorkReportTypes(); if ( result.isEmpty() ) { result.add(getDefaultWorkReportType()); } else { result.add(0, getDefaultWorkReportType()); } return result; } public List<WorkReportType> getWorkReportTypes() { List<WorkReportType> result = workReportModel.getWorkReportTypes(); if ( !result.isEmpty() ) { this.firstType = result.get(2); } return result; } public WorkReportType getDefaultWorkReportType() { return workReportModel.getDefaultType(); } /** * Apply filter to work reports. */ public void onApplyFilter() { createPredicate(); filterByPredicate(); } public Constraint checkConstraintFinishDate() { return (comp, value) -> { Date finishDate = (Date) value; if ( (finishDate != null) && (filterStartDate.getValue() != null) && (finishDate.compareTo(filterStartDate.getValue()) < 0) ) { filterFinishDate.setValue(null); throw new WrongValueException(comp, _("must be later than start date")); } }; } public Constraint checkConstraintStartDate() { return (comp, value) -> { Date startDate = (Date) value; if ( (startDate != null) && (filterFinishDate.getValue() != null) && (startDate.compareTo(filterFinishDate.getValue()) > 0) ) { filterStartDate.setValue(null); throw new WrongValueException(comp, _("must be before end date")); } }; } private void createPredicate() { WorkReportType type = getSelectedType(); Date startDate = filterStartDate.getValue(); Date finishDate = filterFinishDate.getValue(); predicate = new WorkReportPredicate(type, startDate, finishDate); } private WorkReportType getSelectedType() { Listitem itemSelected = listType.getSelectedItem(); if ( (itemSelected != null) && (!java.util.Objects.equals(itemSelected.getValue(), getDefaultWorkReportType())) ) { return (WorkReportType) itemSelected.getValue(); } return null; } private void filterByPredicate() { List<WorkReportDTO> filterWorkReports = workReportModel.getFilterWorkReportDTOs(predicate); listing.setModel(new SimpleListModel<>(filterWorkReports.toArray())); listing.invalidate(); } private void clearFilterDates() { filterStartDate.setValue(null); filterFinishDate.setValue(null); } public List<OrderElement> getOrderElements() { return workReportModel.getOrderElements(); } /** * Methods improved the work report edition and creation. * Executed on pressing New work report button Creates a new work report for a type, * and added it to the work report list. */ /** * It should be public! */ public void onCreateNewWorkReport() { Listitem selectedItem = listTypeToAssign.getSelectedItem(); if ( selectedItem == null ) { throw new WrongValueException(listTypeToAssign, _("please, select a timesheet template type")); } WorkReportType type = selectedItem.getValue(); if ( type == null ) { throw new WrongValueException(listTypeToAssign, _("please, select a timesheet template type")); } goToCreateForm(type); listTypeToAssign.clearSelection(); cameBackList = true; } /** * It should be public! */ public WorkReportType getFirstType() { return firstType; } public void setFirstType(WorkReportType firstType) { this.firstType = firstType; } /** * It should be public! */ public void newWorkReportWithSameType() { if ( save() ) { goToCreateForm(workReportModel.getWorkReportType()); cameBackList = true; } } public void onCheckGenerateCode(Event e) { CheckEvent ce = (CheckEvent) e; if ( ce.isChecked() ) { // We have to auto-generate the code for new objects try { workReportModel.setCodeAutogenerated(ce.isChecked()); } catch (ConcurrentModificationException err) { messagesForUser.showMessage(Level.ERROR, err.getMessage()); } } Util.reloadBindings(createWindow); reloadWorkReportLines(); } /** * It should be public! */ public List<Worker> getBoundWorkers() { return workReportModel.getBoundWorkers(); } /** * It should be public! */ public void createOrEditPersonalTimesheet() { Date date = personalTimesheetsDatebox.getValue(); if ( date == null ) { throw new WrongValueException(personalTimesheetsDatebox, _("Please set a date")); } Resource resource = (Resource) personalTimesheetsBandboxSearch.getSelectedElement(); if ( resource == null ) { throw new WrongValueException(personalTimesheetsBandboxSearch, _("Please select a worker")); } personalTimesheetController.goToCreateOrEditFormForResource(LocalDate.fromDateFields(date), resource); } }