/* * 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.materials; import org.libreplan.business.materials.entities.Material; import org.libreplan.business.materials.entities.MaterialAssignment; import org.libreplan.business.materials.entities.MaterialCategory; import org.libreplan.business.materials.entities.UnitType; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.web.common.Util; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.SuspendNotAllowedException; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Grid; import org.zkoss.zul.Label; import org.zkoss.zul.Listbox; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.ListitemRenderer; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Row; import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Tab; import org.zkoss.zul.Tree; import org.zkoss.zul.TreeModel; import org.zkoss.zul.Treecell; import org.zkoss.zul.Treeitem; import org.zkoss.zul.TreeitemRenderer; import org.zkoss.zul.Treerow; import org.zkoss.zul.Vbox; import org.zkoss.zul.Textbox; import org.zkoss.zul.Decimalbox; import org.zkoss.zul.impl.MessageboxDlg; import java.math.BigDecimal; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.libreplan.web.I18nHelper._; /** * @author Óscar González Fernández <ogonzalez@igalia.com> */ public abstract class AssignedMaterialsController<T, A> extends GenericForwardComposer<Component> { private Tree categoriesTree; private Tree allCategoriesTree; private Grid gridMaterials; private Listbox lbFoundMaterials; private Textbox txtSearchMaterial; private Tab tbAssignedMaterials; private Vbox assignmentsBox; private UnitTypeListRenderer unitTypeListRenderer = new UnitTypeListRenderer(); protected abstract IAssignedMaterialsModel<T, A> getModel(); @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); getModel().loadUnitTypes(); createAssignmentsBoxComponent(assignmentsBox); } protected abstract void createAssignmentsBoxComponent(Component parent); public void openWindow(T element) { initializeEdition(element); prepareCategoriesTree(); prepareAllCategoriesTree(); Util.createBindingsFor(self); Util.reloadBindings(self); } protected abstract void initializeEdition(T orderElement); /** * Delay initialization of categories tree till user clicks on Materials tab. * * Initializing model and renderer properties directly in ZUL resulted in calling the renderer * more times than actually needed, resulting in noticeable lack of performance. */ private void prepareCategoriesTree() { if ( categoriesTree.getItemRenderer() == null ) { categoriesTree.setItemRenderer(getMaterialCategoryWithUnitsAndPriceRenderer()); } categoriesTree.setModel(getMaterialCategories()); } public abstract TreeModel getMaterialCategories(); private void prepareAllCategoriesTree() { if ( allCategoriesTree.getItemRenderer() == null ) { allCategoriesTree.setItemRenderer(getMaterialCategoryRenderer()); } allCategoriesTree.setModel(getAllMaterialCategories()); } public abstract TreeModel getAllMaterialCategories(); public abstract BigDecimal getTotalUnits(); public abstract BigDecimal getTotalPrice(); /** * On selecting category, refresh {@link MaterialAssignment} associated with selected {@link MaterialCategory}. */ public void refreshMaterialAssignments() { final List<A> materials = getAssignedMaterials(); gridMaterials.setModel(new SimpleListModel<>(materials)); reloadGridMaterials(); } public List<A> getAssignedMaterials() { final Treeitem treeitem = categoriesTree.getSelectedItem(); return getAssignedMaterials(treeitem); } private List<A> getAssignedMaterials(Treeitem treeitem) { final MaterialCategory materialCategory = (treeitem != null) ? (MaterialCategory) treeitem.getValue() : null; return getAssignedMaterials(materialCategory); } private List<A> getAssignedMaterials(MaterialCategory materialCategory) { return getModel().getAssignedMaterials(materialCategory); } /** * On changing total price, recalculate unit price and refresh categories tree. * * @param row */ public void updateTotalPrice(Row row) { final A materialAssignment = row.getValue(); reloadGridMaterials(); refreshTotalPriceAndTotalUnits(materialAssignment); } protected abstract Double getTotalPrice(A materialAssignment); /** * Refresh categoriesTree since it shows totalUnits and totalPrice as well. */ private void refreshTotalPriceAndTotalUnits(A materialAssignment) { final Treeitem item = findMaterialCategoryInTree(getCategory(materialAssignment), categoriesTree); if ( item != null ) { // Reload categoriesTree categoriesTree.setModel(getMaterialCategories()); } } private Treeitem findMaterialCategoryInTree(MaterialCategory category, Tree tree) { for (Treeitem treeitem : tree.getItems()) { final MaterialCategory materialCategory = treeitem.getValue(); if ( category.equals(materialCategory) ) { return treeitem; } } return null; } private MaterialCategory getCategory(A assignment) { return getMaterial(assignment).getCategory(); } /** * Search materials on pressing search button. */ public void searchMaterials() { final String text = txtSearchMaterial.getValue(); final MaterialCategory materialCategory = getSelectedCategory(allCategoriesTree); getModel().searchMaterials(text, materialCategory); Util.reloadBindings(lbFoundMaterials); } /** * Returns {@link MaterialCategory} associated with selected {@link Treeitem} in {@link Tree}. * * @param tree * @return {@link MaterialCategory} */ private MaterialCategory getSelectedCategory(Tree tree) { final Treeitem treeitem = tree.getSelectedItem(); return (treeitem != null) ? (MaterialCategory) treeitem.getValue() : null; } /** * Get materials found on latest search. * * @return {@link List<Material>} */ public List<Material> getMatchingMaterials() { return getModel().getMatchingMaterials(); } /** * Assigns a list of selected {@link Material} to current {@link OrderElement}. */ public void assignSelectedMaterials() { Set<Material> materials = getSelectedMaterials(); if ( materials.isEmpty() ) { return; } for(Material each: materials) { getModel().addMaterialAssignment(each); } categoriesTree.clearSelection(); tbAssignedMaterials.setSelected(true); lbFoundMaterials.clearSelection(); Util.reloadBindings(categoriesTree); reloadGridMaterials(); } private Set<Material> getSelectedMaterials() { Set<Material> result = new HashSet<>(); final Set<Listitem> listitems = lbFoundMaterials.getSelectedItems(); for (Listitem each: listitems) { final Material material = each.getValue(); result.add(material); } return result; } public void clearSelectionCategoriesTree() { categoriesTree.clearSelection(); reloadGridMaterials(); } private void reloadGridMaterials() { if ( gridMaterials != null ) { Util.reloadBindings(gridMaterials); } } public void clearSelectionAllCategoriesTree() { allCategoriesTree.clearSelection(); retrieveAllMaterials(); Util.reloadBindings(lbFoundMaterials); } private void retrieveAllMaterials() { getModel().searchMaterials("", null); } /** Should be public! */ public MaterialCategoryRenderer getMaterialCategoryRenderer() { return new MaterialCategoryRenderer(); } private class MaterialCategoryRenderer implements TreeitemRenderer { /** * Copied verbatim from org.zkoss.zul.Tree; */ @Override public void render(Treeitem treeitem, Object o, int i) throws Exception { final MaterialCategory materialCategory = (MaterialCategory) o; Label lblName = new Label(materialCategory.getName()); Treerow tr; treeitem.setValue(o); if ( treeitem.getTreerow() == null ) { tr = new Treerow(); tr.setParent(treeitem); treeitem.setOpen(true); // Expand node } else { tr = treeitem.getTreerow(); tr.getChildren().clear(); } // Add category name Treecell cellName = new Treecell(); cellName.addEventListener("onClick", event -> { getModel().searchMaterials("", materialCategory); Util.reloadBindings(lbFoundMaterials); }); lblName.setParent(cellName); cellName.setParent(tr); } } public MaterialCategoryWithUnitsAndPriceRenderer getMaterialCategoryWithUnitsAndPriceRenderer() { return new MaterialCategoryWithUnitsAndPriceRenderer(); } private class MaterialCategoryWithUnitsAndPriceRenderer implements TreeitemRenderer { /** * Copied verbatim from org.zkoss.zul.Tree; */ @Override public void render(Treeitem treeitem, Object o, int i) throws Exception { final MaterialCategory materialCategory = (MaterialCategory) o; Label lblName = new Label(materialCategory.getName()); Label lblUnits = new Label(getUnits(materialCategory).toString()); Label lblPrice = new Label(getPrice(materialCategory).toString() + getCurrencySymbol()); Treerow tr; treeitem.setValue(o); if ( treeitem.getTreerow() == null ) { tr = new Treerow(); tr.setParent(treeitem); treeitem.setOpen(true); // Expand node } else { tr = treeitem.getTreerow(); tr.getChildren().clear(); } // Add category name Treecell cellName = new Treecell(); lblName.setParent(cellName); cellName.setParent(tr); // Add total assigned material units in category Treecell cellUnits = new Treecell(); lblUnits.setParent(cellUnits); cellUnits.setParent(tr); // Add total price for assigned materials in category Treecell cellPrice = new Treecell(); lblPrice.setParent(cellPrice); cellPrice.setParent(tr); } private BigDecimal getUnits(MaterialCategory materialCategory) { return getModel().getUnits(materialCategory); } private BigDecimal getPrice(MaterialCategory materialCategory) { return getModel().getPrice(materialCategory); } } /** * On clicking remove {@link MaterialAssignment}, shows dialog for confirming removing selected element. * * @param materialAssignment */ public void showRemoveMaterialAssignmentDlg(A materialAssignment) { int status = Messagebox.show( _("Delete item {0}. Are you sure?", getMaterial(materialAssignment).getCode()), _("Delete"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION); if ( Messagebox.OK == status ) { removeMaterialAssignment(materialAssignment); } } protected abstract Material getMaterial(A materialAssignment); private void removeMaterialAssignment(A materialAssignment) { getModel().removeMaterialAssignment(materialAssignment); reloadGridMaterials(); reloadTree(categoriesTree); } private void reloadTree(Tree tree) { final Treeitem treeitem = tree.getSelectedItem(); if ( treeitem != null ) { final MaterialCategory materialCategory = treeitem.getValue(); tree.setModel(getMaterialCategories()); locateAndSelectMaterialCategory(tree, materialCategory); } else { tree.setModel(getMaterialCategories()); reloadGridMaterials(); } } private boolean locateAndSelectMaterialCategory(Tree tree, MaterialCategory materialCategory) { Treeitem treeitem = findTreeItemByMaterialCategory(tree.getRoot(), materialCategory); if ( treeitem != null ) { treeitem.setSelected(true); return true; } return false; } @SuppressWarnings("unchecked") private Treeitem findTreeItemByMaterialCategory(Component node, MaterialCategory materialCategory) { if ( node instanceof Treeitem ) { final Treeitem treeitem = (Treeitem) node; final MaterialCategory _materialCategory = treeitem.getValue(); if ( _materialCategory.getId().equals(materialCategory.getId()) ) { return treeitem; } } for (Component obj : node.getChildren()) { if (obj != null) { Treeitem treeitem = findTreeItemByMaterialCategory(obj, materialCategory); if ( treeitem != null ) { return treeitem; } } } return null; } /** * On clicking Split button, shows dialog for splitting selected {@link MaterialAssignment} into two. * * @param materialAssignment */ @SuppressWarnings("unchecked") public void showSplitMaterialAssignmentDlg(A materialAssignment) { MessageboxDlg dialogSplitAssignment; final String message = _("Do you want to split the material assignment {0}?", getMaterial(materialAssignment).getCode()); Map<String, java.io.Serializable> args = new HashMap<>(); args.put("message", message); args.put("title", _("Split new assignment")); args.put("OK", Messagebox.OK); args.put("CANCEL", Messagebox.CANCEL); args.put("icon", Messagebox.QUESTION); dialogSplitAssignment = (MessageboxDlg) Executions.createComponents("/orders/_splitMaterialAssignmentDlg.zul", self, args); Decimalbox dbUnits = (Decimalbox) dialogSplitAssignment.getFellowIfAny("dbUnits"); dbUnits.setValue(getUnits(materialAssignment)); try { dialogSplitAssignment.doModal(); Messagebox.Button status = dialogSplitAssignment.getResult(); if ( Messagebox.Button.OK == status ) { splitMaterialAssignment(materialAssignment, dbUnits.getValue()); } } catch (SuspendNotAllowedException e) { throw new RuntimeException(e); } } /** * Creates a new {@link MaterialAssignment} out of materialAssignment, but setting its units attribute to units. * * @param materialAssignment * @param units */ private void splitMaterialAssignment(A materialAssignment, BigDecimal units) { A newAssignment = copyFrom(materialAssignment); BigDecimal currentUnits = getUnits(materialAssignment); BigDecimal newUnits = units; if ( units.compareTo(currentUnits) > 0 ) { newUnits = currentUnits; currentUnits = BigDecimal.ZERO; } else { currentUnits = currentUnits.subtract(newUnits); } setUnits(newAssignment, newUnits); setUnits(materialAssignment, currentUnits); getModel().addMaterialAssignment(newAssignment); reloadGridMaterials(); } protected abstract void setUnits(A assignment, BigDecimal units); protected abstract A copyFrom(A assignment); protected abstract BigDecimal getUnits(A assignment); public List<UnitType> getUnitTypes() { return getModel().getUnitTypes(); } public void selectUnitType(Component self) { Listitem selectedItem = ((Listbox) self).getSelectedItem(); UnitType unitType = selectedItem.getValue(); Material material = ((Row) self.getParent()).getValue(); material.setUnitType(unitType); } public UnitTypeListRenderer getRenderer() { return unitTypeListRenderer; } public class UnitTypeListRenderer implements ListitemRenderer { /** * RowRenderer for a @{UnitType} element. * * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> */ @Override public void render(Listitem listitem, Object o, int i) throws Exception { final UnitType unitType = (UnitType) o; listitem.setValue(unitType); Listcell listCell = new Listcell(unitType.getMeasure()); listitem.appendChild(listCell); Listbox listbox = listitem.getListbox(); Component parent = listbox.getParent(); if ( parent instanceof Row ) { Object assignment = ((Row) parent).getValue(); if ( getModel().isCurrentUnitType(assignment, unitType) ) { listitem.getListbox().setSelectedItem(listitem); } return; } if ( parent instanceof Listcell ) { Material material = ((Listitem) (parent.getParent())).getValue(); if ( isCurrentUnitType(material, unitType) ) { listitem.getListbox().setSelectedItem(listitem); } } } private boolean isCurrentUnitType(Material material, UnitType unitType) { return material != null && material.getUnitType() != null && unitType.getId().equals(material.getUnitType().getId()); } } public String getCurrencySymbol() { return Util.getCurrencySymbol(); } public String getMoneyFormat() { return Util.getMoneyFormat(); } }