diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/FilterPair.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/FilterPair.java index 2c24259c9..be3ca8746 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/FilterPair.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/FilterPair.java @@ -25,7 +25,7 @@ package org.navalplanner.web.common.components.finders; */ public class FilterPair extends Object { - private OrderFilterEnum type; + private IFilterEnum type; private String pattern; @@ -34,13 +34,13 @@ public class FilterPair extends Object { public FilterPair() { } - public FilterPair(OrderFilterEnum type, String pattern, Object value) { + public FilterPair(IFilterEnum type, String pattern, Object value) { this.type = type; this.value = value; this.pattern = pattern; } - public OrderFilterEnum getType() { + public IFilterEnum getType() { return type; } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/IFilterEnum.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/IFilterEnum.java new file mode 100644 index 000000000..6dac4617c --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/IFilterEnum.java @@ -0,0 +1,5 @@ +package org.navalplanner.web.common.components.finders; + +public interface IFilterEnum { + public String toString(); +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/MultipleFiltersFinder.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/MultipleFiltersFinder.java index c7a627edb..d84449c33 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/MultipleFiltersFinder.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/MultipleFiltersFinder.java @@ -449,9 +449,9 @@ public class MultipleFiltersFinder implements IMultipleFiltersFinder { } filterValues = updateDeletedFilters(filterValues, value); - + value = value.replace(" ", ""); String[] values = value.split(","); - if (values.length != filterValues.size() + 1) { + if (values.length != filterValues.size()) { return false; } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/OrderFilterEnum.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/OrderFilterEnum.java index d75590d55..cd7006da2 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/OrderFilterEnum.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/OrderFilterEnum.java @@ -23,7 +23,7 @@ */ package org.navalplanner.web.common.components.finders; -public enum OrderFilterEnum { +public enum OrderFilterEnum implements IFilterEnum { None("..."), Criterion("Criterion"), Label("Label"), ExternalCompany( "Customer"), State( diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourceFilterEnum.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourceFilterEnum.java new file mode 100644 index 000000000..6a76838f1 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourceFilterEnum.java @@ -0,0 +1,41 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +/** + * @author Susana Montes Pedreira + */ +package org.navalplanner.web.common.components.finders; + +public enum ResourceFilterEnum implements IFilterEnum { + + None("..."), Criterion("Criterion"), CostCategory("Cost category"); + + private String description; + + private ResourceFilterEnum(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + +} \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourcesMultipleFiltersFinder.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourcesMultipleFiltersFinder.java new file mode 100644 index 000000000..6a5ea09d0 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/finders/ResourcesMultipleFiltersFinder.java @@ -0,0 +1,333 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.common.components.finders; + +import static org.navalplanner.web.I18nHelper._; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.navalplanner.business.common.IAdHocTransactionService; +import org.navalplanner.business.common.IOnTransaction; +import org.navalplanner.business.costcategories.daos.ICostCategoryDAO; +import org.navalplanner.business.costcategories.entities.CostCategory; +import org.navalplanner.business.resources.daos.ICriterionDAO; +import org.navalplanner.business.resources.daos.ICriterionTypeDAO; +import org.navalplanner.business.resources.daos.IResourceDAO; +import org.navalplanner.business.resources.entities.Criterion; +import org.navalplanner.business.resources.entities.CriterionType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.zkoss.zul.Listcell; +import org.zkoss.zul.Listitem; +import org.zkoss.zul.ListitemRenderer; + +/** + * Implements all the methods needed to search the criterion to filter the + * resources. Provides multiples criterions to filter like {@link Criterion}, + * {@link Category} or filter by name or nif. + * @author Susana Montes Pedreira + */ + +public class ResourcesMultipleFiltersFinder implements IMultipleFiltersFinder { + + @Autowired + private ICriterionTypeDAO criterionTypeDAO; + + @Autowired + private ICriterionDAO criterionDAO; + + @Autowired + private ICostCategoryDAO costCategoryDAO; + + @Autowired + private IResourceDAO resourceDAO; + + @Autowired + private IAdHocTransactionService adHocTransactionService; + + private static final Map> mapCriterions = new HashMap>(); + + private static final List costCategories = new ArrayList(); + + private List listMatching = new ArrayList(); + + private final String headers[] = { _("Filter type"), _("Filter pattern") }; + + protected ResourcesMultipleFiltersFinder() { + + } + + @Transactional(readOnly = true) + public void init() { + adHocTransactionService + .runOnReadOnlyTransaction(new IOnTransaction() { + @Override + public Void execute() { + loadCriterions(); + loadCostCategories(); + return null; + } + }); + } + + private void loadCriterions() { + mapCriterions.clear(); + List criterionTypes = criterionTypeDAO + .getCriterionTypes(); + for (CriterionType criterionType : criterionTypes) { + List criterions = new ArrayList(criterionDAO + .findByType(criterionType)); + + mapCriterions.put(criterionType, criterions); + } + } + + private void loadCostCategories() { + costCategories.clear(); + costCategories.addAll(costCategoryDAO.findActive()); + } + + public List getFirstTenFilters() { + listMatching.clear(); + fillWithFirstTenFiltersCriterions(); + fillWithFirstTenFiltersCostCategories(); + listMatching.add(new FilterPair(OrderFilterEnum.None, + OrderFilterEnum.None.toString(), null)); + return listMatching; + } + + private List fillWithFirstTenFiltersCriterions() { + Iterator iteratorCriterionType = mapCriterions.keySet() + .iterator(); + while (iteratorCriterionType.hasNext() && listMatching.size() < 10) { + CriterionType type = iteratorCriterionType.next(); + for (int i = 0; listMatching.size() < 10 + && i < mapCriterions.get(type).size(); i++) { + Criterion criterion = mapCriterions.get(type).get(i); + addCriterion(type, criterion); + } + } + return listMatching; + } + + private List fillWithFirstTenFiltersCostCategories() { + for (int i = 0; listMatching.size() < 10 + && i < costCategories.size(); i++) { + CostCategory costCategory = costCategories.get(i); + addCostCategory(costCategory); + } + return listMatching; + } + + public List getMatching(String filter) { + listMatching.clear(); + if ((filter != null) && (!filter.isEmpty())) { + filter = filter.toLowerCase(); + searchInCriterionTypes(filter); + searchInCostCategories(filter); + } + addNoneFilter(); + return listMatching; + } + + private void searchInCriterionTypes(String filter) { + boolean limited = (filter.length() < 3); + for (CriterionType type : mapCriterions.keySet()) { + if (type.getName().toLowerCase().contains(filter)) { + setFilterPairCriterionType(type, limited); + } else { + searchInCriterions(type, filter); + } + } + } + + private void searchInCriterions(CriterionType type, String filter) { + for (Criterion criterion : mapCriterions.get(type)) { + if (criterion.getName().toLowerCase().contains(filter)) { + addCriterion(type, criterion); + if ((filter.length() < 3) && (listMatching.size() > 9)) { + return; + } + } + } + } + + private void setFilterPairCriterionType(CriterionType type, boolean limited) { + for (Criterion criterion : mapCriterions.get(type)) { + addCriterion(type, criterion); + if ((limited) && (listMatching.size() > 9)) { + return; + } + } + } + + private void searchInCostCategories(String filter) { + for (CostCategory costCategory : costCategories) { + if (costCategory.getName().toLowerCase().contains(filter)) { + addCostCategory(costCategory); + if ((filter.length() < 3) && (listMatching.size() > 9)) { + return; + } + } + } + } + + private void addCriterion(CriterionType type, Criterion criterion) { + String pattern = type.getName() + " :: " + criterion.getName(); + listMatching.add(new FilterPair(ResourceFilterEnum.Criterion, pattern, + criterion)); + } + + private void addCostCategory(CostCategory costCategory) { + String pattern = costCategory.getName(); + listMatching.add(new FilterPair(ResourceFilterEnum.CostCategory, + pattern, costCategory)); + } + + private void addNoneFilter() { + listMatching.add(new FilterPair(ResourceFilterEnum.None, + ResourceFilterEnum.None.toString(), null)); + } + + public String objectToString(Object obj) { + FilterPair filterPair = (FilterPair) obj; + String text = filterPair.getType() + "(" + filterPair.getPattern() + + "), "; + return text; + } + + @Override + public String getNewFilterText(String inputText) { + String newFilterText = new String(""); + String[] filtersText = inputText.split(","); + newFilterText = getLastText(filtersText); + newFilterText = newFilterText.replace(" ", ""); + newFilterText = newFilterText.trim(); + return newFilterText; + } + + private String getLastText(String[] texts) { + Integer last = texts.length - 1; + if (texts.length > 0) { + return texts[last]; + } else { + return ""; + } + } + + public boolean isValidNewFilter(Object obj) { + FilterPair filter = (FilterPair) obj; + if (filter.getType().equals(OrderFilterEnum.None)) { + return false; + } + return true; + } + + public boolean isValidFormatText(List filterValues, String value) { + if (filterValues.isEmpty()) { + return true; + } + + filterValues = updateDeletedFilters(filterValues, value); + value = value.replace(" ", ""); + String[] values = value.split(","); + if (values.length != filterValues.size()) { + return false; + } + + int i = 0; + for (FilterPair filterPair : (List) filterValues) { + String filterPairText = filterPair.getType() + "(" + + filterPair.getPattern() + ")"; + if (!isFilterAdded(values, filterPairText)) { + return false; + } + i++; + } + return true; + } + + @Override + public List updateDeletedFilters(List filterValues, String value) { + String[] values = value.split(","); + List listFilters = (List) filterValues; + List list = new ArrayList(); + list.addAll(listFilters); + + if (values.length < filterValues.size() + 1) { + for (FilterPair filterPair : list) { + String filter = filterPair.getType() + "(" + + filterPair.getPattern() + ")"; + if (!isFilterAdded(values, filter)) { + listFilters.remove(filterPair); + } + } + } + return listFilters; + } + + private boolean isFilterAdded(String[] values, String filter) { + for (int i = 0; i < values.length; i++) { + String value = values[i].replace(" ", ""); + filter = filter.replace(" ", ""); + + if (filter.equals(value)) { + return true; + } + } + return false; + } + + public String[] getHeaders() { + return headers; + } + + public ListitemRenderer getItemRenderer() { + return filterPairRenderer; + } + + /** + * Render for {@link FilterPair} + * @author Susana Montes Pedreira + */ + private final ListitemRenderer filterPairRenderer = new ListitemRenderer() { + + @Override + public void render(Listitem item, Object data) throws Exception { + FilterPair filterPair = (FilterPair) data; + item.setValue(data); + + final Listcell labelType = new Listcell(); + labelType.setLabel(filterPair.getType().toString()); + labelType.setParent(item); + + final Listcell labelPattern = new Listcell(); + labelPattern.setLabel(filterPair.getPattern()); + labelPattern.setParent(item); + + } + }; + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/orders/OrderPredicate.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/orders/OrderPredicate.java index ead663b3b..92332de70 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/orders/OrderPredicate.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/orders/OrderPredicate.java @@ -33,6 +33,7 @@ import org.navalplanner.business.resources.entities.Criterion; import org.navalplanner.business.workreports.entities.WorkReport; import org.navalplanner.business.workreports.entities.WorkReportType; import org.navalplanner.web.common.components.finders.FilterPair; +import org.navalplanner.web.common.components.finders.OrderFilterEnum; /** * Checks if {@link WorkReportType}, the start date and finish date from @@ -87,7 +88,7 @@ public class OrderPredicate implements IPredicate { } private boolean acceptFilter(FilterPair filter,Order order){ - switch (filter.getType()) { + switch ((OrderFilterEnum) filter.getType()) { case Criterion: return acceptCriterion(filter, order); case Label: diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/IMachineModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/IMachineModel.java index f51d3e3e7..1ccf8e5c7 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/IMachineModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/IMachineModel.java @@ -30,6 +30,7 @@ import org.navalplanner.business.resources.entities.Criterion; import org.navalplanner.business.resources.entities.Machine; import org.navalplanner.business.resources.entities.MachineWorkersConfigurationUnit; import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.web.resources.search.ResourcePredicate; /* * This interface contains the operations to create/edit a machine. @@ -95,4 +96,7 @@ public interface IMachineModel { BaseCalendar getDefaultCalendar(); + List getFilteredMachines(ResourcePredicate predicate); + + public List getAllMachines(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineCRUDController.java index f6b7015d7..ca7b37bed 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineCRUDController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineCRUDController.java @@ -28,6 +28,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.validator.InvalidValue; +import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.BaseCalendar; import org.navalplanner.business.calendars.entities.ResourceCalendar; import org.navalplanner.business.common.exceptions.ValidationException; @@ -40,8 +41,11 @@ import org.navalplanner.web.common.Level; import org.navalplanner.web.common.MessagesForUser; import org.navalplanner.web.common.OnlyOneVisible; import org.navalplanner.web.common.Util; +import org.navalplanner.web.common.components.bandboxsearch.BandboxMultipleSearch; +import org.navalplanner.web.common.components.finders.FilterPair; import org.navalplanner.web.common.entrypoints.IURLHandlerRegistry; import org.navalplanner.web.costcategories.ResourcesCostCategoryAssignmentController; +import org.navalplanner.web.resources.search.ResourcePredicate; import org.navalplanner.web.resources.worker.CriterionsController; import org.navalplanner.web.resources.worker.CriterionsMachineController; import org.zkoss.zk.ui.Component; @@ -50,7 +54,12 @@ import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Combobox; import org.zkoss.zul.Comboitem; import org.zkoss.zul.ComboitemRenderer; +import org.zkoss.zul.Constraint; +import org.zkoss.zul.Datebox; +import org.zkoss.zul.Grid; +import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Tab; +import org.zkoss.zul.Textbox; import org.zkoss.zul.api.Window; /** @@ -82,6 +91,16 @@ public class MachineCRUDController extends GenericForwardComposer { private ResourcesCostCategoryAssignmentController resourcesCostCategoryAssignmentController; + private Grid listing; + + private Datebox filterStartDate; + + private Datebox filterFinishDate; + + private Textbox txtfilter; + + private BandboxMultipleSearch bdFilters; + private static final Log LOG = LogFactory .getLog(MachineCRUDController.class); @@ -108,12 +127,25 @@ public class MachineCRUDController extends GenericForwardComposer { setupConfigurationController(); setupResourcesCostCategoryAssignmentController(comp); showListWindow(); + initFilterComponent(); } private void showListWindow() { getVisibility().showOnly(listWindow); } + private void initFilterComponent() { + this.filterFinishDate = (Datebox) listWindow + .getFellowIfAny("filterFinishDate"); + this.filterStartDate = (Datebox) listWindow + .getFellowIfAny("filterStartDate"); + this.bdFilters = (BandboxMultipleSearch) listWindow + .getFellowIfAny("bdFilters"); + this.txtfilter = (Textbox) listWindow.getFellowIfAny("txtfilter"); + this.listing = (Grid) listWindow.getFellowIfAny("listing"); + clearFilterDates(); + } + private OnlyOneVisible getVisibility() { if (visibility == null) { visibility = new OnlyOneVisible(listWindow, editWindow); @@ -416,4 +448,94 @@ public class MachineCRUDController extends GenericForwardComposer { } + /** + * Operations to filter the machines by multiple filters + */ + + public Constraint checkConstraintFinishDate() { + return new Constraint() { + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + Date finishDate = (Date) value; + if ((finishDate != null) + && (filterStartDate.getValue() != null) + && (finishDate.compareTo(filterStartDate.getValue()) < 0)) { + filterFinishDate.setValue(null); + throw new WrongValueException(comp, + _("must be greater than start date")); + } + } + }; + } + + public Constraint checkConstraintStartDate() { + return new Constraint() { + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + Date startDate = (Date) value; + if ((startDate != null) + && (filterFinishDate.getValue() != null) + && (startDate.compareTo(filterFinishDate.getValue()) > 0)) { + filterStartDate.setValue(null); + throw new WrongValueException(comp, + _("must be lower than finish date")); + } + } + }; + } + + public void onApplyFilter() { + ResourcePredicate predicate = createPredicate(); + if (predicate != null) { + filterByPredicate(predicate); + } else { + showAllMachines(); + } + } + + private ResourcePredicate createPredicate() { + List listFilters = (List) bdFilters + .getSelectedElements(); + + String personalFilter = txtfilter.getValue(); + // Get the dates filter + LocalDate startDate = null; + LocalDate finishDate = null; + if (filterStartDate.getValue() != null) { + startDate = LocalDate.fromDateFields(filterStartDate + .getValue()); + } + if (filterFinishDate.getValue() != null) { + finishDate = LocalDate.fromDateFields(filterFinishDate + .getValue()); + } + + if (listFilters.isEmpty() + && (personalFilter == null || personalFilter.isEmpty()) + && startDate == null && finishDate == null) { + return null; + } + return new ResourcePredicate(listFilters, personalFilter, startDate, + finishDate); + } + + private void filterByPredicate(ResourcePredicate predicate) { + List filteredResources = machineModel + .getFilteredMachines(predicate); + listing.setModel(new SimpleListModel(filteredResources.toArray())); + listing.invalidate(); + } + + private void clearFilterDates() { + filterStartDate.setValue(null); + filterFinishDate.setValue(null); + } + + public void showAllMachines() { + listing.setModel(new SimpleListModel(machineModel.getAllMachines() + .toArray())); + listing.invalidate(); + } } \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineModel.java index b30642cc4..0796c8821 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/machine/MachineModel.java @@ -51,6 +51,7 @@ import org.navalplanner.business.resources.entities.MachineWorkersConfigurationU import org.navalplanner.business.resources.entities.ResourceEnum; import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.web.calendars.IBaseCalendarModel; +import org.navalplanner.web.resources.search.ResourcePredicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; @@ -80,6 +81,7 @@ public class MachineModel implements IMachineModel { private Machine machine; private Map criterions = new HashMap(); private Map workers = new HashMap(); + private List machineList = new ArrayList(); @Autowired private IResourceDAO resourceDAO; @@ -255,7 +257,8 @@ public class MachineModel implements IMachineModel { @Override @Transactional(readOnly = true) public List getMachines() { - return machineDAO.getAll(); + machineList = machineDAO.getAll(); + return machineList; } public MachineWorkersConfigurationUnit getConfigurationUnitById(Long id) @@ -316,4 +319,20 @@ public class MachineModel implements IMachineModel { baseCalendar.getExceptions().size(); } + @Override + @Transactional(readOnly = true) + public List getFilteredMachines(ResourcePredicate predicate) { + List filteredResourceList = new ArrayList(); + for (Machine machine : machineList) { + machineDAO.reattach(machine); + if (predicate.accepts(machine)) { + filteredResourceList.add(machine); + } + } + return filteredResourceList; + } + + public List getAllMachines() { + return machineList; + } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/search/ResourcePredicate.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/search/ResourcePredicate.java new file mode 100644 index 000000000..6746efcaa --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/search/ResourcePredicate.java @@ -0,0 +1,239 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.resources.search; + +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.Interval; +import org.joda.time.LocalDate; +import org.navalplanner.business.calendars.entities.CalendarAvailability; +import org.navalplanner.business.costcategories.entities.CostCategory; +import org.navalplanner.business.costcategories.entities.ResourcesCostCategoryAssignment; +import org.navalplanner.business.resources.entities.Criterion; +import org.navalplanner.business.resources.entities.CriterionSatisfaction; +import org.navalplanner.business.resources.entities.Machine; +import org.navalplanner.business.resources.entities.Resource; +import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.web.common.components.finders.FilterPair; +import org.navalplanner.web.common.components.finders.ResourceFilterEnum; +import org.navalplanner.web.orders.IPredicate; + +/** + * Checks if {@link Resource} matches with this predicate. + * @author Susana Montes Pedreira + */ + +public class ResourcePredicate implements IPredicate { + + private List filters; + + private LocalDate startDate; + + private LocalDate finishDate; + + private String[] personalFilters; + + private List assignedResourcesInIntervalDates; + + public ResourcePredicate(List filters, String personalFilters, + LocalDate startDate, + LocalDate finishDate) { + this.filters = filters; + this.startDate = startDate; + this.finishDate = finishDate; + this.personalFilters = personalFilters.split(" "); + } + + @Override + public boolean accepts(Object object) { + final Resource resource = (Resource) object; + return accepts(resource); + } + + private boolean accepts(Resource resource) { + if (resource == null) { + return false; + } + if (acceptFilters(resource) && acceptPersonalFilters(resource) + && acceptFiltersDates(resource)) { + return true; + } + return false; + } + + private boolean acceptFilters(Resource resource) { + if ((filters == null) || (filters.isEmpty())) { + return true; + } + for (FilterPair filter : filters) { + if (!acceptFilter(filter, resource)) { + return false; + } + } + return true; + } + + private boolean acceptFilter(FilterPair filter, Resource resource) { + switch ((ResourceFilterEnum) filter.getType()) { + case Criterion: + return acceptCriterion(filter, resource); + case CostCategory: + return acceptCostCategory(filter, resource); + } + return false; + } + + private boolean acceptCriterion(FilterPair filter, Resource resource) { + Criterion filterCriterion = (Criterion) filter.getValue(); + for (CriterionSatisfaction criterionSatisfaction : resource + .getCriterionSatisfactions()) { + if (criterionSatisfaction.getCriterion().getId().equals( + filterCriterion.getId())) { + return true; + } + } + return false; + } + + private boolean acceptCostCategory(FilterPair filter, Resource resource) { + CostCategory filterCostCategory = (CostCategory) filter.getValue(); + for (ResourcesCostCategoryAssignment assignedCostCategory : resource + .getResourcesCostCategoryAssignments()) { + if (assignedCostCategory.getCostCategory().getId().equals( + filterCostCategory.getId())) { + return true; + } + } + return false; + } + + private boolean acceptPersonalFilters(Resource resource) { + for (String filter : personalFilters) { + filter = filter.replace(" ", ""); + filter = filter.toLowerCase(); + if (filter.isEmpty()) { + continue; + } + + if ((!acceptName(filter, resource)) + && (!acceptApel(filter, resource)) + && (!acceptNif(filter, resource)) + && (!acceptCode(filter, resource))) { + return false; + } + } + return true; + } + + private boolean acceptName(String filterName, Resource resource) { + if (resource instanceof Worker) { + return ((Worker) resource).getFirstName().toLowerCase().contains( + filterName); + } + return resource.getName().toLowerCase().contains(filterName); + } + + private boolean acceptApel(String filterApel, Resource resource) { + if (resource instanceof Worker) { + return ((Worker) resource).getSurname().toLowerCase().contains( + filterApel); + } + return false; + } + + private boolean acceptCode(String filterCode, Resource resource) { + if (resource instanceof Machine) { + return ((Machine) resource).getCode().toLowerCase().contains( + filterCode); + } + return false; + } + + private boolean acceptNif(String filterNif, Resource resource) { + if (resource instanceof Worker) { + return ((Worker) resource).getNif().toLowerCase().contains( + filterNif); + } + return false; + } + + private boolean acceptFiltersDates(Resource resource) { + // Check if exist some day of the active period into interval between + // the start date and finish date. + if (startDate == null && finishDate == null) { + return true; + } + if ((resource.getCalendar() != null)) { + for (CalendarAvailability calendar : resource.getCalendar() + .getCalendarAvailabilities()) { + if (isOverlap(calendar)) { + return true; + } + } + } + return false; + } + + private boolean isOverlap(CalendarAvailability calendar) { + if (calendar.getEndDate() == null) { + if (finishDate != null) { + return (finishDate.compareTo(calendar.getStartDate()) >= 0); + } else { + return true; + } + } else { + if (finishDate == null) { + return (startDate.compareTo(calendar.getEndDate()) <= 0); + } + if (startDate == null) { + return (finishDate.compareTo(calendar.getStartDate()) >= 0); + } + } + Interval filter = getIntervalFilter(); + Interval activePeriod = getIntervalActivePeriod(calendar); + return filter.overlaps(activePeriod); + } + + private Interval getIntervalFilter() { + DateTime startDateTime = null; + if (startDate != null) { + startDateTime = startDate.toDateTimeAtStartOfDay(); + } + + DateTime endDateTime = null; + if (finishDate != null) { + endDateTime = (finishDate.plusDays(1)).toDateTimeAtStartOfDay(); + } + return new Interval(startDateTime, endDateTime); + } + + private Interval getIntervalActivePeriod(CalendarAvailability calendar) { + DateTime endDateTime = null; + if (calendar.getEndDate() != null) { + endDateTime = (calendar.getEndDate().plusDays(1)) + .toDateTimeAtStartOfDay(); + } + return new Interval(calendar.getStartDate().toDateTimeAtStartOfDay(), + endDateTime); + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java index c2ccd52a0..8f2a69692 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java @@ -34,6 +34,7 @@ import org.navalplanner.business.resources.entities.ICriterion; import org.navalplanner.business.resources.entities.ICriterionType; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.web.resources.search.ResourcePredicate; /** * This interface contains the operations to create/edit a worker. The @@ -137,4 +138,8 @@ public interface IWorkerModel { void setCapacity(Integer capacity); + public List getFilteredWorker(ResourcePredicate predicate); + + public List getAllCurrentWorkers(); + } \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java index 11a0d69d2..8829033cb 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java @@ -26,6 +26,7 @@ import static org.navalplanner.web.common.ConcurrentModificationDetector.addAuto import java.util.Date; import java.util.List; +import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.BaseCalendar; import org.navalplanner.business.calendars.entities.ResourceCalendar; import org.navalplanner.business.common.exceptions.ValidationException; @@ -39,16 +40,24 @@ import org.navalplanner.web.common.Level; import org.navalplanner.web.common.MessagesForUser; import org.navalplanner.web.common.OnlyOneVisible; import org.navalplanner.web.common.Util; +import org.navalplanner.web.common.components.bandboxsearch.BandboxMultipleSearch; +import org.navalplanner.web.common.components.finders.FilterPair; import org.navalplanner.web.common.entrypoints.IURLHandlerRegistry; import org.navalplanner.web.common.entrypoints.URLHandler; import org.navalplanner.web.costcategories.ResourcesCostCategoryAssignmentController; +import org.navalplanner.web.resources.search.ResourcePredicate; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Combobox; import org.zkoss.zul.Comboitem; import org.zkoss.zul.ComboitemRenderer; +import org.zkoss.zul.Constraint; +import org.zkoss.zul.Datebox; +import org.zkoss.zul.Grid; +import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Tab; +import org.zkoss.zul.Textbox; import org.zkoss.zul.api.Window; /** @@ -93,6 +102,16 @@ public class WorkerCRUDController extends GenericForwardComposer implements private BaseCalendarsComboitemRenderer baseCalendarsComboitemRenderer = new BaseCalendarsComboitemRenderer(); + private Grid listing; + + private Datebox filterStartDate; + + private Datebox filterFinishDate; + + private BandboxMultipleSearch bdFilters; + + private Textbox txtfilter; + public WorkerCRUDController() { } @@ -255,6 +274,19 @@ public class WorkerCRUDController extends GenericForwardComposer implements .getRedirectorFor(IWorkerCRUDControllerEntryPoints.class); handler.registerListener(this, page); getVisibility().showOnly(listWindow); + initFilterComponent(); + } + + private void initFilterComponent() { + this.filterFinishDate = (Datebox) listWindow + .getFellowIfAny("filterFinishDate"); + this.filterStartDate = (Datebox) listWindow + .getFellowIfAny("filterStartDate"); + this.bdFilters = (BandboxMultipleSearch) listWindow + .getFellowIfAny("bdFilters"); + this.txtfilter = (Textbox) listWindow.getFellowIfAny("txtfilter"); + this.listing = (Grid) listWindow.getFellowIfAny("listing"); + clearFilterDates(); } private void setupResourcesCostCategoryAssignmentController(Component comp) @@ -495,4 +527,93 @@ public class WorkerCRUDController extends GenericForwardComposer implements this.workerModel.setCapacity(capacity); } + /** + * Operations to filter the machines by multiple filters + */ + + public Constraint checkConstraintFinishDate() { + return new Constraint() { + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + Date finishDate = (Date) value; + if ((finishDate != null) + && (filterStartDate.getValue() != null) + && (finishDate.compareTo(filterStartDate.getValue()) < 0)) { + filterFinishDate.setValue(null); + throw new WrongValueException(comp, + _("must be greater than start date")); + } + } + }; + } + + public Constraint checkConstraintStartDate() { + return new Constraint() { + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + Date startDate = (Date) value; + if ((startDate != null) + && (filterFinishDate.getValue() != null) + && (startDate.compareTo(filterFinishDate.getValue()) > 0)) { + filterStartDate.setValue(null); + throw new WrongValueException(comp, + _("must be lower than finish date")); + } + } + }; + } + + public void onApplyFilter() { + ResourcePredicate predicate = createPredicate(); + if (predicate != null) { + filterByPredicate(predicate); + } else { + showAllWorkers(); + } + } + + private ResourcePredicate createPredicate() { + List listFilters = (List) bdFilters + .getSelectedElements(); + + String personalFilter = txtfilter.getValue(); + + // Get the dates filter + LocalDate startDate = null; + LocalDate finishDate = null; + if (filterStartDate.getValue() != null) { + startDate = LocalDate.fromDateFields(filterStartDate.getValue()); + } + if (filterFinishDate.getValue() != null) { + finishDate = LocalDate.fromDateFields(filterFinishDate.getValue()); + } + + if (listFilters.isEmpty() + && (personalFilter == null || personalFilter.isEmpty()) + && startDate == null && finishDate == null) { + return null; + } + return new ResourcePredicate(listFilters, personalFilter, startDate, + finishDate); + } + + private void filterByPredicate(ResourcePredicate predicate) { + List filteredResources = workerModel + .getFilteredWorker(predicate); + listing.setModel(new SimpleListModel(filteredResources.toArray())); + listing.invalidate(); + } + + private void clearFilterDates() { + filterStartDate.setValue(null); + filterFinishDate.setValue(null); + } + + public void showAllWorkers() { + listing.setModel(new SimpleListModel(workerModel.getAllCurrentWorkers() + .toArray())); + listing.invalidate(); + } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java index 631edbdbe..4ac5b3427 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java @@ -54,6 +54,7 @@ import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.VirtualWorker; import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.web.calendars.IBaseCalendarModel; +import org.navalplanner.web.resources.search.ResourcePredicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; @@ -95,6 +96,8 @@ public class WorkerModel implements IWorkerModel { @Autowired private IConfigurationDAO configurationDAO; + private List currentWorkerList = new ArrayList(); + @Autowired public WorkerModel(IResourceDAO resourceDAO, ICriterionDAO criterionDAO) { @@ -128,7 +131,8 @@ public class WorkerModel implements IWorkerModel { @Override @Transactional(readOnly = true) public List getRealWorkers() { - return resourceDAO.getRealWorkers(); + currentWorkerList = resourceDAO.getRealWorkers(); + return currentWorkerList; } @Override @@ -138,7 +142,8 @@ public class WorkerModel implements IWorkerModel { for (Worker each : list) { each.getCalendar().getCapacity(); } - return list; + currentWorkerList = list; + return currentWorkerList; } @Override @@ -541,4 +546,20 @@ public class WorkerModel implements IWorkerModel { return defaultCalendar; } + @Override + @Transactional(readOnly = true) + public List getFilteredWorker(ResourcePredicate predicate) { + List filteredResourceList = new ArrayList(); + for (Worker worker : currentWorkerList) { + resourceDAO.reattach(worker); + if (predicate.accepts(worker)) { + filteredResourceList.add(worker); + } + } + return filteredResourceList; + } + + public List getAllCurrentWorkers() { + return currentWorkerList; + } } diff --git a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml index 8abc41e1e..55e42901c 100644 --- a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml +++ b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml @@ -42,6 +42,9 @@ + + diff --git a/navalplanner-webapp/src/main/webapp/resources/machine/_listMachines.zul b/navalplanner-webapp/src/main/webapp/resources/machine/_listMachines.zul index 5a07bb078..173724b4a 100644 --- a/navalplanner-webapp/src/main/webapp/resources/machine/_listMachines.zul +++ b/navalplanner-webapp/src/main/webapp/resources/machine/_listMachines.zul @@ -17,8 +17,16 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - + + + +
+ +
+ + + diff --git a/navalplanner-webapp/src/main/webapp/resources/search/_resourceFilter.zul b/navalplanner-webapp/src/main/webapp/resources/search/_resourceFilter.zul new file mode 100644 index 000000000..3b5f3e728 --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/resources/search/_resourceFilter.zul @@ -0,0 +1,15 @@ + +