org.libreplan.web.orders.materials.AssignedMaterialsController.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.orders.materials.AssignedMaterialsController.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico 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 static org.libreplan.web.I18nHelper._;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.LogFactory;
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.event.Event;
import org.zkoss.zk.ui.event.EventListener;
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.api.Decimalbox;
import org.zkoss.zul.api.Textbox;
import org.zkoss.zul.impl.MessageboxDlg;

/**
 * @author scar Gonzlez Fernndez <ogonzalez@igalia.com>
 *
 */
public abstract class AssignedMaterialsController<T, A> extends GenericForwardComposer {

    private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(AssignedMaterialsController.class);

    private Tree categoriesTree;

    private Tree allCategoriesTree;

    private Grid gridMaterials;

    private Listbox lbFoundMaterials;

    private Textbox txtSearchMaterial;

    private Tab tbAssignedMaterials;

    private Vbox assignmentsBox;

    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.getTreeitemRenderer() == null) {
            categoriesTree.setTreeitemRenderer(getMaterialCategoryWithUnitsAndPriceRenderer());
        }
        categoriesTree.setModel(getMaterialCategories());
    }

    public abstract TreeModel getMaterialCategories();

    private void prepareAllCategoriesTree() {
        if (allCategoriesTree.getTreeitemRenderer() == null) {
            allCategoriesTree.setTreeitemRenderer(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 refreshMaterialAssigments() {
        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);
    }

    public 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 = (A) 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 (Iterator i = tree.getItems().iterator(); i.hasNext();) {
            Treeitem treeitem = (Treeitem) i.next();
            final MaterialCategory 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
     */
    private MaterialCategory getSelectedCategory(Tree tree) {
        final Treeitem treeitem = tree.getSelectedItem();
        return (treeitem != null) ? (MaterialCategory) treeitem.getValue() : null;
    }

    /**
     * Get materials found on latest search
     *
     * @return
     */
    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<Material>();

        final Set<Listitem> listitems = lbFoundMaterials.getSelectedItems();
        for (Listitem each : listitems) {
            final Material 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);
    }

    public MaterialCategoryRenderer getMaterialCategoryRenderer() {
        return new MaterialCategoryRenderer();
    }

    private class MaterialCategoryRenderer implements TreeitemRenderer {

        /**
         * Copied verbatim from org.zkoss.zul.Tree;
         */
        @Override
        public void render(Treeitem ti, Object node) {
            final MaterialCategory materialCategory = (MaterialCategory) node;

            Label lblName = new Label(materialCategory.getName());

            Treerow tr = null;
            ti.setValue(node);
            if (ti.getTreerow() == null) {
                tr = new Treerow();
                tr.setParent(ti);
                ti.setOpen(true); // Expand node
            } else {
                tr = ti.getTreerow();
                tr.getChildren().clear();
            }
            // Add category name
            Treecell cellName = new Treecell();
            cellName.addEventListener("onClick", new EventListener() {

                @Override
                public void onEvent(Event 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 ti, Object node) {
            final MaterialCategory materialCategory = (MaterialCategory) node;

            Label lblName = new Label(materialCategory.getName());
            Label lblUnits = new Label(getUnits(materialCategory).toString());
            Label lblPrice = new Label(getPrice(materialCategory).toString() + getCurrencySymbol());

            Treerow tr = null;
            ti.setValue(node);
            if (ti.getTreerow() == null) {
                tr = new Treerow();
                tr.setParent(ti);
                ti.setOpen(true); // Expand node
            } else {
                tr = ti.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) {
        try {
            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);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    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 = (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 = (MaterialCategory) treeitem.getValue();
            if (_materialCategory.getId().equals(materialCategory.getId())) {
                return treeitem;
            }
        }
        for (Iterator i = node.getChildren().iterator(); i.hasNext();) {
            Object obj = i.next();
            if (obj instanceof Component) {
                Treeitem treeitem = findTreeItemByMaterialCategory((Component) 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 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();
            int status = dialogSplitAssignment.getResult();
            if (Messagebox.OK == status) {
                splitMaterialAssignment(materialAssignment, dbUnits.getValue());
            }
        } catch (SuspendNotAllowedException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Creates a new {@link MaterialAssignment} out of materialAssignment, but
     * setting its units attribute to units.
     *
     * materialAssignment passed as parameter decreases its units attribute in units
     *
     * @param materialAssignment
     * @param units
     */
    private void splitMaterialAssignment(A materialAssignment, BigDecimal units) {
        A newAssignment = copyFrom(materialAssignment);
        BigDecimal currentUnits = getUnits(materialAssignment);
        if (units.compareTo(currentUnits) > 0) {
            units = currentUnits;
            currentUnits = BigDecimal.ZERO;
        } else {
            currentUnits = currentUnits.subtract(units);
        }
        setUnits(newAssignment, units);
        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);

    private UnitTypeListRenderer unitTypeListRenderer = new UnitTypeListRenderer();

    public List<UnitType> getUnitTypes() {
        return getModel().getUnitTypes();
    }

    public void selectUnitType(Component self) {
        Listitem selectedItem = ((Listbox) self).getSelectedItem();
        UnitType unitType = (UnitType) selectedItem.getValue();
        Material material = (Material) ((Row) self.getParent()).getValue();
        material.setUnitType(unitType);
    }

    public UnitTypeListRenderer getRenderer() {
        return unitTypeListRenderer;
    }

    /**
     * RowRenderer for a @{UnitType} element
     * @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
     */
    public class UnitTypeListRenderer implements ListitemRenderer {
        @Override
        public void render(Listitem listItem, Object data) {
            final UnitType unitType = (UnitType) data;
            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 assigment = (Object) ((Row) parent).getValue();
                if (getModel().isCurrentUnitType(assigment, unitType)) {
                    listItem.getListbox().setSelectedItem(listItem);
                }
                return;
            }

            if (parent instanceof Listcell) {
                Material 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();
    }

}