/* * 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.orders; import static org.libreplan.web.I18nHelper._; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.libreplan.business.common.daos.IConnectorDAO; import org.libreplan.business.common.entities.Connector; import org.libreplan.business.common.entities.EntitySequence; import org.libreplan.business.common.entities.PredefinedConnectorProperties; import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.labels.entities.Label; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderLine; import org.libreplan.business.orders.entities.OrderLineGroup; import org.libreplan.business.orders.entities.SchedulingState; import org.libreplan.business.requirements.entities.CriterionRequirement; import org.libreplan.business.users.entities.UserRole; import org.libreplan.web.common.FilterUtils; import org.libreplan.web.common.IMessagesForUser; import org.libreplan.web.common.Level; import org.libreplan.web.common.Util; import org.libreplan.web.common.components.bandboxsearch.BandboxMultipleSearch; import org.libreplan.web.common.components.bandboxsearch.BandboxSearch; import org.libreplan.web.common.components.finders.FilterPair; import org.libreplan.web.common.components.finders.OrderElementFilterEnum; import org.libreplan.web.common.components.finders.TaskElementFilterEnum; import org.libreplan.web.orders.assigntemplates.TemplateFinderPopup; import org.libreplan.web.security.SecurityUtils; import org.libreplan.web.templates.IOrderTemplatesControllerEntryPoints; import org.libreplan.web.tree.TreeController; 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.zkplus.spring.SpringUtil; import org.zkoss.zul.A; import org.zkoss.zul.Button; import org.zkoss.zul.Checkbox; import org.zkoss.zul.Constraint; import org.zkoss.zul.Datebox; import org.zkoss.zul.Popup; import org.zkoss.zul.Tab; import org.zkoss.zul.Textbox; import org.zkoss.zul.TreeModel; import org.zkoss.zul.Treechildren; import org.zkoss.zul.Treeitem; import org.zkoss.zul.Vbox; import org.zkoss.zul.Treerow; import org.zkoss.zul.impl.InputElement; /** * Controller for {@link OrderElement} tree view of {@link Order} entities. * <br /> * @author Lorenzo Tilve Álvaro <ltilve@igalia.com> * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Diego Pino García <dpino@igalia.com> * @author Vova Perebykivskyi <vova@libreplan-enterprise.com> */ public class OrderElementTreeController extends TreeController<OrderElement> { private Vbox orderElementFilter; private BandboxMultipleSearch bdFiltersOrderElement; private Datebox filterStartDateOrderElement; private Datebox filterFinishDateOrderElement; private Checkbox labelsWithoutInheritance; private Textbox filterNameOrderElement; private OrderElementTreeitemRenderer renderer = new OrderElementTreeitemRenderer(); private final IOrderModel orderModel; private final OrderElementController orderElementController; private transient IPredicate predicate; private OrderElementOperations operationsForOrderElement; private final IMessagesForUser messagesForUser; private IConnectorDAO connectorDAO; private Tab tabGeneralData; private TemplateFinderPopup templateFinderPopup; public OrderElementTreeController(IOrderModel orderModel, OrderElementController orderElementController, IMessagesForUser messagesForUser) { super(OrderElement.class); this.orderModel = orderModel; this.orderElementController = orderElementController; this.messagesForUser = messagesForUser; initializeOperationsForOrderElement(); } public List<Label> getLabels() { return orderModel.getLabels(); } @Override public OrderElementTreeitemRenderer getRenderer() { return renderer; } /** * Initializes operationsForOrderTemplate. * A reference to variables tree and orderTemplates will be set later in doAfterCompose() */ private void initializeOperationsForOrderElement() { operationsForOrderElement = OrderElementOperations .build() .treeController(this) .orderModel(this.orderModel) .orderElementController(this.orderElementController); } public OrderElementController getOrderElementController() { return orderElementController; } @Override protected OrderElementTreeModel getModel() { return orderModel.getOrderElementTreeModel(); } /** * Operations for each node. */ public void editSelectedElement() { operationsForOrderElement.editSelectedElement(); } public void createTemplateFromSelectedElement() { operationsForOrderElement.createTemplateFromSelectedElement(); } public void moveSelectedElementUp() { operationsForOrderElement.moveSelectedElementUp(); } public void moveSelectedElementDown() { operationsForOrderElement.moveSelectedElementDown(); } public void indentSelectedElement() { operationsForOrderElement.indentSelectedElement(); } public void unindentSelectedElement() { operationsForOrderElement.unindentSelectedElement(); } public void deleteSelectedElement() { operationsForOrderElement.deleteSelectedElement(); } public void createFromTemplate() { templateFinderPopup.openForSubElemenetCreation(tree, "after_pointer", template -> { OrderLineGroup parent = (OrderLineGroup) getModel().getRoot(); orderModel.createFrom(parent, template); getModel().addNewlyAddedChildrenOf(parent); reloadTreeUIAfterChanges(); }); } @Override protected void reloadTreeUIAfterChanges() { tree.setModel(getFilteredTreeModel()); tree.onInitRender(); tree.invalidate(); } void doEditFor() { Util.reloadBindings(tree); } public void disabledCodeBoxes(boolean disabled) { Set<Treeitem> childrenSet = new HashSet<>(); Treechildren treeChildren = tree.getTreechildren(); if ( treeChildren != null ) { childrenSet.addAll(treeChildren.getItems()); } for (Treeitem each : childrenSet) { disableCodeBoxes(each, disabled); } } private void disableCodeBoxes(Treeitem item, boolean disabled) { Treerow row = item.getTreerow(); InputElement codeBox = (InputElement) (row.getChildren().get(1)).getChildren().get(0); codeBox.setDisabled(disabled); codeBox.invalidate(); Set<Treeitem> childrenSet = new HashSet<>(); Treechildren children = item.getTreechildren(); if ( children != null ) { childrenSet.addAll(children.getItems()); } for (Treeitem each : childrenSet) { disableCodeBoxes(each, disabled); } } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); orderElementFilter.getChildren().clear(); appendExpandCollapseButton(); connectorDAO = (IConnectorDAO) SpringUtil.getBean("connectorDAO"); // Configuration of the order elements filter Component filterComponent = Executions.createComponents( "/orders/_orderElementTreeFilter.zul", orderElementFilter, new HashMap<String, String>()); IOrderTemplatesControllerEntryPoints orderTemplates = (IOrderTemplatesControllerEntryPoints) SpringUtil.getBean("orderTemplates"); filterComponent.setAttribute("treeController", this, true); bdFiltersOrderElement = (BandboxMultipleSearch) filterComponent.getFellow("bdFiltersOrderElement"); Popup filterOptionsPopup = (Popup) filterComponent.getFellow("filterOptionsPopup"); filterStartDateOrderElement = (Datebox) filterOptionsPopup.getFellow("filterStartDateOrderElement"); filterFinishDateOrderElement = (Datebox) filterOptionsPopup.getFellow("filterFinishDateOrderElement"); labelsWithoutInheritance = (Checkbox) filterOptionsPopup.getFellow("labelsWithoutInheritance"); filterNameOrderElement = (Textbox) filterComponent.getFellow("filterNameOrderElement"); labelsWithoutInheritance = (Checkbox) filterComponent.getFellow("labelsWithoutInheritance"); templateFinderPopup = (TemplateFinderPopup) comp.getFellow("templateFinderPopupAtTree"); operationsForOrderElement.tree(tree).orderTemplates(orderTemplates); importOrderFiltersFromSession(); disableCreateTemplateButtonIfNeeded(comp); } private void importOrderFiltersFromSession() { Order order = orderModel.getOrder(); filterNameOrderElement.setValue(FilterUtils.readOrderTaskName(order)); filterStartDateOrderElement.setValue(FilterUtils.readOrderStartDate(order)); filterFinishDateOrderElement.setValue(FilterUtils.readOrderEndDate(order)); if ( FilterUtils.readOrderParameters(order) != null ) { for (FilterPair each : FilterUtils.readOrderParameters(order)) { if ( toOrderFilterEnum(each) != null ) { bdFiltersOrderElement.addSelectedElement(toOrderFilterEnum(each)); } } } if ( FilterUtils.readOrderInheritance(order) != null ) { labelsWithoutInheritance.setChecked(FilterUtils.readOrderInheritance(order)); } } private FilterPair toOrderFilterEnum(FilterPair each) { switch ((TaskElementFilterEnum) each.getType()) { case Label: return new FilterPair(OrderElementFilterEnum.Label, each.getPattern(), each.getValue()); case Criterion: return new FilterPair(OrderElementFilterEnum.Criterion, each.getPattern(), each.getValue()); case Resource: // Resources are discarded on WBS filter default: return null; } } private void disableCreateTemplateButtonIfNeeded(Component comp) { Button createTemplateButton = (Button) comp.getFellowIfAny("createTemplateButton"); if ( createTemplateButton != null ) { if ( !SecurityUtils.isSuperuserOrUserInRoles(UserRole.ROLE_TEMPLATES) ) { createTemplateButton.setDisabled(true); createTemplateButton.setTooltiptext(_("Not enough permissions to create templates")); } } } private void appendExpandCollapseButton() { List<Component> children = orderElementFilter.getParent().getChildren(); Button button = (Button) ComponentsFinder.findById("expandAllButton", children); if ( button != null ) { if ( button.getSclass().equals("planner-command clicked") ) { button.setSclass("planner-command"); button.invalidate(); } return; } // Append expand/collapse button final Button expandAllButton = new Button(); expandAllButton.setId("expandAllButton"); expandAllButton.setClass("planner-command"); expandAllButton.setTooltiptext(_("Expand/Collapse all")); expandAllButton.setImage("/common/img/ico_expand.png"); expandAllButton.addEventListener("onClick", event -> { if ( expandAllButton.getSclass().equals("planner-command") ) { expandAll(); expandAllButton.setSclass("planner-command clicked"); } else { collapseAll(); expandAllButton.setSclass("planner-command"); } }); children.add(expandAllButton); } public void expandAll() { Set<Treeitem> childrenSet = new HashSet<>(); Treechildren children = tree.getTreechildren(); if ( children != null ) { childrenSet.addAll(children.getItems()); } childrenSet.forEach(this::expandAll); } private void expandAll(Treeitem item) { item.setOpen(true); Set<Treeitem> childrenSet = new HashSet<>(); Treechildren children = item.getTreechildren(); if ( children != null ) { childrenSet.addAll(children.getItems()); } childrenSet.forEach(this::expandAll); } public void collapseAll() { Treechildren children = tree.getTreechildren(); for (Treeitem each: (children.getItems())) { each.setOpen(false); } } public Map<OrderElement, Textbox> getOrderElementCodeTextboxes() { return getRenderer().getCodeTextboxByElement(); } public class OrderElementTreeitemRenderer extends Renderer { public OrderElementTreeitemRenderer() {} @Override protected void addDescriptionCell(OrderElement element) { addTaskNameCell(element); } private void addTaskNameCell(final OrderElement orderElementForThisRow) { int[] path = getModel().getPath(orderElementForThisRow); String cssClass = "depth_" + path.length; Textbox textBox = Util.bind( new Textbox(), () -> orderElementForThisRow.getName(), value -> orderElementForThisRow.setName(value) ); if ( readOnly ) { textBox.setDisabled(true); } textBox.setConstraint("no empty:" + _("cannot be empty")); addCell(cssClass, textBox); putNameTextbox(orderElementForThisRow, textBox); } @Override protected SchedulingState getSchedulingStateFrom(OrderElement currentElement) { return currentElement.getSchedulingState(); } @Override protected void onDoubleClickForSchedulingStateCell(final OrderElement currentOrderElement) { IOrderElementModel model = orderModel.getOrderElementModel(currentOrderElement); orderElementController.openWindow(model); updateColumnsFor(currentOrderElement); } @Override protected void addCodeCell(final OrderElement orderElement) { if ( orderElement.isJiraIssue() ) { addHyperlink(orderElement); } else { addTextbox(orderElement); } } private void addTextbox(final OrderElement orderElement) { Textbox textBoxCode = new Textbox(); Util.bind( textBoxCode, () -> orderElement.getCode(), value -> orderElement.setCode(value) ); textBoxCode.setConstraint((comp, value) -> { if ( !orderElement.isFormatCodeValid((String) value) ) { throw new WrongValueException( comp, _("Value is not valid.\n Code cannot contain chars like '_' \n " + "and should not be empty")); } }); if ( orderModel.isCodeAutogenerated() || readOnly ) { textBoxCode.setDisabled(true); } addCell(textBoxCode); putCodeTextbox(orderElement, textBoxCode); } private void addHyperlink(final OrderElement orderElement) { String code = orderElement.getCode(); A hyperlink = new A(code); Connector connector = connectorDAO.findUniqueByName(PredefinedConnectors.JIRA.getName()); if ( connector == null ) { return; } String jiraUrl = connector.getPropertiesAsMap().get(PredefinedConnectorProperties.SERVER_URL); String codeWithoutPrefix = StringUtils.removeStart(code, PredefinedConnectorProperties.JIRA_CODE_PREFIX); codeWithoutPrefix = StringUtils.removeStart(codeWithoutPrefix, orderElement.getOrder().getCode() + EntitySequence.CODE_SEPARATOR_CHILDREN); hyperlink.setHref(jiraUrl + "/browse/" + codeWithoutPrefix); if ( orderModel.isCodeAutogenerated() || readOnly ) { hyperlink.setDisabled(true); } addCell(hyperlink); } void addInitDateCell(final OrderElement currentOrderElement) { DynamicDatebox dynamicDatebox = new DynamicDatebox( () -> currentOrderElement.getInitDate(), value -> currentOrderElement.setInitDate(value)); if ( readOnly ) { dynamicDatebox.setDisabled(true); } addDateCell("init-date-cell", dynamicDatebox); putInitDateDynamicDatebox(currentOrderElement, dynamicDatebox); reduceWidthOfDateBoxes(dynamicDatebox); } void addEndDateCell(final OrderElement currentOrderElement) { DynamicDatebox dynamicDatebox = new DynamicDatebox( () -> currentOrderElement.getDeadline(), value -> currentOrderElement.setDeadline(value)); if ( readOnly || (currentOrderElement.getTaskSource() != null && currentOrderElement.getTaskSource().getTask().isSubcontracted()) ) { dynamicDatebox.setDisabled(true); } addDateCell("end-date-cell", dynamicDatebox); putEndDateDynamicDatebox(currentOrderElement, dynamicDatebox); reduceWidthOfDateBoxes(dynamicDatebox); } /** * Decrease width of components in ZK8. */ private void reduceWidthOfDateBoxes(DynamicDatebox dynamicDatebox) { Textbox textbox = dynamicDatebox.getDateTextBox(); String[] strings = textbox.getWidth().split("px"); textbox.setWidth( Integer.toString(Integer.valueOf(strings[0]) - 10) ); } @Override protected void addOperationsCell(final Treeitem item, final OrderElement currentOrderElement) { addCell(createEditButton(item), createRemoveButton(currentOrderElement)); } private Button createEditButton(final Treeitem item) { return createButton( "/common/img/ico_editar1.png", _("Edit"), "/common/img/ico_editar.png", "icono", event -> showEditionOrderElement(item)); } @Override public void removeCodeTextbox(OrderElement key) { super.removeCodeTextbox(key); } public void addResourcesBudgetCell(final OrderElement currentElement) { BigDecimal value = currentElement.getSubstractedBudget(); Textbox autoBudgetCell = new Textbox(Util.addCurrencySymbol(value)); autoBudgetCell.setDisabled(true); addCell(autoBudgetCell); } } @Override protected boolean isPredicateApplied() { return (predicate != null) && !((OrderElementPredicate) predicate).isEmpty(); } /** * Apply filter to order elements in current order. */ public void onApplyFilter() { writeFilterParameters(); OrderElementPredicate predicate = createPredicate(); this.predicate = predicate; if ( predicate != null ) { filterByPredicate(predicate); } else { showAllOrderElements(); } } private void writeFilterParameters() { Order order = orderModel.getOrder(); FilterUtils.writeOrderStartDate(order, filterStartDateOrderElement.getValue()); FilterUtils.writeOrderEndDate(order, filterFinishDateOrderElement.getValue()); FilterUtils.writeOrderTaskName(order, filterNameOrderElement.getValue()); FilterUtils.writeOrderInheritance(order, labelsWithoutInheritance.isChecked()); List<FilterPair> result = new ArrayList<>(); for (FilterPair filterPair : (List<FilterPair>) bdFiltersOrderElement.getSelectedElements()) { result.add(toTasKElementFilterEnum(filterPair)); } FilterUtils.writeOrderParameters(order, result); FilterUtils.writeOrderWBSFiltersChanged(order, true); } private FilterPair toTasKElementFilterEnum(FilterPair each) { switch ((OrderElementFilterEnum) each.getType()) { case Label: return new FilterPair(TaskElementFilterEnum.Label, each.getPattern(), each.getValue()); case Criterion: return new FilterPair(TaskElementFilterEnum.Criterion, each.getPattern(), each.getValue()); default: return null; } } private OrderElementPredicate createPredicate() { List<FilterPair> listFilters = bdFiltersOrderElement.getSelectedElements(); Date startDate = filterStartDateOrderElement.getValue(); Date finishDate = filterFinishDateOrderElement.getValue(); boolean ignoreLabelsInheritance = labelsWithoutInheritance.isChecked(); String name = filterNameOrderElement.getValue(); return listFilters.isEmpty() && startDate == null && finishDate == null && name == null ? null : new OrderElementPredicate(listFilters, startDate, finishDate, name, ignoreLabelsInheritance); } public TreeModel getFilteredTreeModel() { OrderElementTreeModel filteredModel = getFilteredModel(); return filteredModel == null ? null : filteredModel.asTree(); } public OrderElementTreeModel getFilteredModel() { if (orderModel == null) { return null; } OrderElementPredicate predicate = createPredicate(); this.predicate = predicate; return predicate != null ? orderModel.getOrderElementsFilteredByPredicate(predicate) : orderModel.getOrderElementTreeModel(); } private void filterByPredicate(OrderElementPredicate predicate) { OrderElementTreeModel orderElementTreeModel = orderModel.getOrderElementsFilteredByPredicate(predicate); tree.setModel(orderElementTreeModel.asTree()); tree.invalidate(); } public void showAllOrderElements() { this.predicate = null; tree.setModel(orderModel.getOrderElementTreeModel().asTree()); tree.invalidate(); } @Override protected boolean isNewButtonDisabled() { return readOnly; } /** * Clear {@link BandboxSearch} for Labels, and initializes {@link IPredicate}. */ public void clear() { selectDefaultTab(); bdFiltersOrderElement.clear(); predicate = null; } private void selectDefaultTab() { tabGeneralData.setSelected(true); } @Override protected String createTooltipText(OrderElement elem) { StringBuilder tooltipText = new StringBuilder(); tooltipText.append(elem.getName()).append(". "); if ( (elem.getDescription() != null) && !("".equals(elem.getDescription())) ) { tooltipText.append(elem.getDescription()); tooltipText.append(". "); } if ( (elem.getLabels() != null) && (!elem.getLabels().isEmpty()) ) { tooltipText.append(" ").append(_("Labels")).append(":"); tooltipText.append(StringUtils.join(elem.getLabels(), ",")); tooltipText.append("."); } if ( (elem.getCriterionRequirements() != null) && (!elem.getCriterionRequirements().isEmpty()) ) { ArrayList<String> criterionNames = new ArrayList<>(); for(CriterionRequirement each:elem.getCriterionRequirements()) { if ( each.isValid() ) { criterionNames.add(each.getCriterion().getName()); } } if ( !criterionNames.isEmpty() ) { tooltipText.append(" " + _("Criteria") + ":"); tooltipText.append(StringUtils.join(criterionNames, ",")); tooltipText.append("."); } } // To calculate other unit advances implement getOtherAdvancesPercentage() tooltipText.append(" ").append(_("Progress")).append(":").append(elem.getAdvancePercentage()); tooltipText.append("."); return tooltipText.toString(); } public void showEditionOrderElement(final Treeitem item) { OrderElement currentOrderElement = item.getValue(); markModifiedTreeitem(item.getTreerow()); IOrderElementModel model = orderModel.getOrderElementModel(currentOrderElement); orderElementController.openWindow(model); refreshRow(item); } public void refreshRow(Treeitem item) { try { getRenderer().updateColumnsFor(item.getValue()); getRenderer().render(item, item.getValue(), 0); } catch (Exception e) { e.printStackTrace(); } } public Treeitem getTreeitemByOrderElement(OrderElement element) { List<Treeitem> listItems = new ArrayList<>(this.tree.getItems()); for (Treeitem item : listItems) { OrderElement orderElement = item.getValue(); if ( orderElement.getId().equals(element.getId()) ) { return item; } } return null; } /** * Operations to filter the orders by multiple filters. */ public Constraint checkConstraintFinishDate() { return (comp, value) -> { Date finishDate = (Date) value; if ( (finishDate != null) && (filterStartDateOrderElement.getValue() != null) && (finishDate.compareTo(filterStartDateOrderElement.getValue()) < 0) ) { filterFinishDateOrderElement.setValue(null); throw new WrongValueException(comp, _("must be after start date")); } }; } public Constraint checkConstraintStartDate() { return (comp, value) -> { Date startDate = (Date) value; if ( (startDate != null) && (filterFinishDateOrderElement.getValue() != null) && (startDate.compareTo(filterFinishDateOrderElement.getValue()) > 0) ) { filterStartDateOrderElement.setValue(null); throw new WrongValueException(comp, _("must be lower than end date")); } }; } @Override public void remove(OrderElement element) { boolean hasImputedExpenseSheets = orderModel.hasImputedExpenseSheetsThisOrAnyOfItsChildren(element); if ( hasImputedExpenseSheets ) { messagesForUser.showMessage( Level.ERROR, _("You can not remove the project \"{0}\" because this one has imputed expense sheets.", element.getName())); return; } boolean alreadyInUse = orderModel.isAlreadyInUse(element); if ( alreadyInUse ) { messagesForUser.showMessage( Level.ERROR, _("You cannot remove the task \"{0}\" because it has work reported on it or any of its children", element.getName())); return; } boolean onlyChildAndParentAlreadyInUseByHoursOrExpenses = orderModel.isOnlyChildAndParentAlreadyInUseByHoursOrExpenses(element); if ( onlyChildAndParentAlreadyInUseByHoursOrExpenses ) { messagesForUser.showMessage( Level.ERROR, _("You cannot remove the task \"{0}\" because it is the only child of its parent " + "and its parent has tracked time or imputed expenses", element.getName())); return; } super.remove(element); getRenderer().removeCodeTextbox(element); } @Override protected IHoursGroupHandler<OrderElement> getHoursGroupHandler() { return new IHoursGroupHandler<OrderElement>() { @Override public boolean hasMoreThanOneHoursGroup(OrderElement element) { return element.getHoursGroups().size() > 1; } @Override public boolean isTotalHoursValid(OrderElement line, Integer value) { return ((OrderLine) line).isTotalHoursValid(value); } @Override public Integer getWorkHoursFor(OrderElement element) { return element.getWorkHours(); } @Override public void setWorkHours(OrderElement element, Integer value) { if ( element instanceof OrderLine ) { OrderLine line = (OrderLine) element; line.setWorkHours(value); } } }; } @Override protected IBudgetHandler<OrderElement> getBudgetHandler() { return new IBudgetHandler<OrderElement>() { @Override public BigDecimal getBudgetFor(OrderElement element) { return element.getBudget(); } @Override public void setBudgetHours(OrderElement element, BigDecimal budget) { if ( element instanceof OrderLine ) { OrderLine line = (OrderLine) element; line.setBudget(budget); } } }; } @Override protected INameHandler<OrderElement> getNameHandler() { return element -> element.getName(); } @Override protected ICodeHandler<OrderElement> getCodeHandler() { return element -> element.getCode(); } }