diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/LimitingResourceQueue.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/LimitingResourceQueue.java new file mode 100644 index 000000000..f2f1096e9 --- /dev/null +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/LimitingResourceQueue.java @@ -0,0 +1,213 @@ +/* + * This file is part of NavalPlan + * + * 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.zkoss.ganttz.data.limitingresource; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.joda.time.LocalDate; +import org.zkoss.ganttz.data.resourceload.TimeLineRole; +import org.zkoss.ganttz.util.Interval; + +public class LimitingResourceQueue { + + private final String conceptName; + private final List loadPeriods; + + private final TimeLineRole timeLineRole; + private final String type; + + private final List children; + + public LimitingResourceQueue(String conceptName, + List loadPeriods, + TimeLineRole role) { + Validate.notEmpty(conceptName); + Validate.notNull(loadPeriods); + this.loadPeriods = QueueTask.sort(loadPeriods); + this.conceptName = conceptName; + this.type = ""; + this.timeLineRole = role; + this.children = Collections + .unmodifiableList(new ArrayList()); + } + + public LimitingResourceQueue(String conceptName, + List loadPeriods, + String type, TimeLineRole role) { + Validate.notEmpty(conceptName); + Validate.notNull(loadPeriods); + this.loadPeriods = QueueTask.sort(loadPeriods); + this.conceptName = conceptName; + this.timeLineRole = role; + this.type = type; + this.children = Collections + .unmodifiableList(new ArrayList()); + } + + public LimitingResourceQueue(LimitingResourceQueue principal, List children) { + Validate.notEmpty(principal.getConceptName()); + Validate.notNull(principal.getQueueTasks()); + this.loadPeriods = QueueTask.sort(principal.getQueueTasks()); + this.conceptName = principal.getConceptName(); + this.timeLineRole = principal.getRole(); + this.type = principal.getType(); + Validate.notNull(children); + allChildrenAreNotEmpty(children); + this.children = Collections + .unmodifiableList(new ArrayList(children)); + + } + + public List getQueueTasks() { + return loadPeriods; + } + + public String getConceptName() { + return conceptName; + } + + public TimeLineRole getRole() { + return timeLineRole; + } + + private QueueTask getFirst() { + return loadPeriods.get(0); + } + + private QueueTask getLast() { + return loadPeriods.get(loadPeriods.size() - 1); + } + + public LocalDate getStartPeriod() { + if (isEmpty()) { + return null; + } + return getFirst().getStart(); + } + + public boolean isEmpty() { + return loadPeriods.isEmpty(); + } + + public LocalDate getEndPeriod() { + if (isEmpty()) { + return null; + } + return getLast().getEnd(); + } + + public String getType() { + return this.type; + } + + public static Interval getIntervalFrom(List timeLines) { + Validate.notEmpty(timeLines); + LocalDate start = null; + LocalDate end = null; + for (LimitingResourceQueue loadTimeLine : timeLines) { + Validate.notNull(loadTimeLine.getStart()); + start = min(start, loadTimeLine.getStart()); + Validate.notNull(loadTimeLine.getEnd()); + end = max(end, loadTimeLine.getEnd()); + } + return new Interval(toDate(start), toDate(end)); + } + + private static Date toDate(LocalDate localDate) { + return localDate.toDateTimeAtStartOfDay().toDate(); + } + + private static LocalDate max(LocalDate one, LocalDate other) { + if (one == null) { + return other; + } + if (other == null) { + return one; + } + return one.compareTo(other) > 0 ? one : other; + } + + private static LocalDate min(LocalDate one, LocalDate other) { + if (one == null) { + return other; + } + if (other == null) { + return one; + } + return one.compareTo(other) < 0 ? one : other; + } + + private static void allChildrenAreNotEmpty(List lines) { + for (LimitingResourceQueue l : lines) { + if (l.isEmpty()) { + throw new IllegalArgumentException(l + " is empty"); + } + if (l.hasChildren()) { + allChildrenAreNotEmpty(l.getChildren()); + } + } + } + + public boolean hasChildren() { + return (!children.isEmpty()); + } + + public List getChildren() { + return children; + } + + public List getAllChildren() { + List result = new ArrayList(); + for (LimitingResourceQueue child : children) { + result.addAll(child.getAllChildren()); + result.add(child); + } + return result; + } + + public LocalDate getStart() { + LocalDate result = getStartPeriod(); + for (LimitingResourceQueue loadTimeLine : getChildren()) { + LocalDate start = loadTimeLine.getStart(); + if (start != null) { + result = result == null || result.compareTo(start) > 0 ? start + : result; + } + } + return result; + } + + public LocalDate getEnd() { + LocalDate result = getEndPeriod(); + for (LimitingResourceQueue loadTimeLine : getChildren()) { + LocalDate end = loadTimeLine.getEnd(); + if (end != null) { + result = result == null || result.compareTo(end) < 0 ? end : result; + } + } + return result; + } + +} diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/QueueTask.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/QueueTask.java new file mode 100644 index 000000000..a6a2f4946 --- /dev/null +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/limitingresource/QueueTask.java @@ -0,0 +1,128 @@ +/* + * This file is part of NavalPlan + * + * 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.zkoss.ganttz.data.limitingresource; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.LocalDate; +import org.zkoss.ganttz.data.resourceload.LoadLevel; + +public class QueueTask { + + private static final Log LOG = LogFactory.getLog(QueueTask.class); + + private final LocalDate start; + + private final LocalDate end; + + private final LoadLevel loadLevel; + + private final int totalResourceWorkHours; + + private final int assignedHours; + + public QueueTask(LocalDate start, LocalDate end, + int totalResourceWorkHours, int assignedHours, LoadLevel loadLevel) { + Validate.notNull(start); + Validate.notNull(end); + Validate.notNull(loadLevel); + Validate.notNull(totalResourceWorkHours); + Validate.notNull(assignedHours); + Validate.isTrue(!start.isAfter(end)); + this.start = start; + this.end = end; + this.loadLevel = loadLevel; + this.totalResourceWorkHours = totalResourceWorkHours; + this.assignedHours = assignedHours; + } + + public LocalDate getStart() { + return start; + } + + public LocalDate getEnd() { + return end; + } + + // public boolean overlaps(QueueTask other) { + // return start.isBefore(other.end) && end.isAfter(other.start); + // } + + /** + * @param loadPeriods + * @return + * @throws IllegalArgumentException + * if some of the QueueTask overlaps + */ + public static List sort(List loadPeriods) + throws IllegalArgumentException { + ArrayList result = new ArrayList(loadPeriods); + // Collections.sort(result, new Comparator() { + + // @Override + // public int compare(QueueTask o1, QueueTask o2) { + // if (o1.overlaps(o2)) { + // LOG.warn(o1 + " overlaps with " + o2); + // throw new IllegalArgumentException(o1 + " overlaps with " + // + o2); + // } + // int comparison = compareLocalDates(o1.start, o2.start); + // if (comparison != 0) { + // return comparison; + // } + // return compareLocalDates(o1.end, o2.end); + // } + // }); + return result; + } + + private static int compareLocalDates(LocalDate l1, LocalDate l2) { + if (l1.isBefore(l2)) { + return -1; + } + if (l1.isAfter(l2)) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + public LoadLevel getLoadLevel() { + return loadLevel; + } + + public int getTotalResourceWorkHours() { + return totalResourceWorkHours; + } + + public int getAssignedHours() { + return assignedHours; + } +} diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesComponent.java b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesComponent.java index 5cf526cc3..b4d76f4fc 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesComponent.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesComponent.java @@ -26,8 +26,8 @@ import java.util.List; import org.joda.time.LocalDate; import org.zkoss.ganttz.IDatesMapper; -import org.zkoss.ganttz.data.resourceload.LoadPeriod; -import org.zkoss.ganttz.data.resourceload.LoadTimeLine; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; +import org.zkoss.ganttz.data.limitingresource.QueueTask; import org.zkoss.ganttz.timetracker.TimeTracker; import org.zkoss.ganttz.timetracker.zoom.IZoomLevelChangedListener; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; @@ -41,33 +41,36 @@ import org.zkoss.zul.impl.XulElement; public class LimitingResourcesComponent extends XulElement { public static LimitingResourcesComponent create(TimeTracker timeTracker, - LoadTimeLine loadLine) { - return new LimitingResourcesComponent(timeTracker, loadLine); + LimitingResourceQueue limitingResourceQueue) { + return new LimitingResourcesComponent(timeTracker, + limitingResourceQueue); } - private final LoadTimeLine loadLine; + private final LimitingResourceQueue loadLine; private final TimeTracker timeTracker; private transient IZoomLevelChangedListener zoomChangedListener; private LimitingResourcesComponent(final TimeTracker timeTracker, - final LoadTimeLine loadLine) { - this.loadLine = loadLine; + final LimitingResourceQueue limitingResourceQueue) { + this.loadLine = limitingResourceQueue; this.timeTracker = timeTracker; - createChildren(loadLine, timeTracker.getMapper()); + createChildren(limitingResourceQueue, timeTracker.getMapper()); zoomChangedListener = new IZoomLevelChangedListener() { @Override public void zoomLevelChanged(ZoomLevel detailLevel) { getChildren().clear(); - createChildren(loadLine, timeTracker.getMapper()); + createChildren(limitingResourceQueue, timeTracker.getMapper()); invalidate(); } }; this.timeTracker.addZoomListener(zoomChangedListener); } - private void createChildren(LoadTimeLine loadLine, IDatesMapper mapper) { - List
divs = createDivsForPeriods(mapper, loadLine.getLoadPeriods()); + private void createChildren(LimitingResourceQueue limitingResourceQueue, + IDatesMapper mapper) { + List
divs = createDivsForPeriods(mapper, limitingResourceQueue + .getQueueTasks()); for (Div div : divs) { appendChild(div); } @@ -83,16 +86,16 @@ public class LimitingResourcesComponent extends XulElement { private static List
createDivsForPeriods(IDatesMapper datesMapper, - List loadPeriods) { + List list) { List
result = new ArrayList
(); - for (LoadPeriod loadPeriod : loadPeriods) { + for (QueueTask loadPeriod : list) { result.add(createDivForPeriod(datesMapper, loadPeriod)); } return result; } private static Div createDivForPeriod(IDatesMapper datesMapper, - LoadPeriod loadPeriod) { + QueueTask loadPeriod) { Div result = new Div(); result.setClass(String.format("taskassignmentinterval %s", loadPeriod .getLoadLevel().getCategory())); @@ -112,7 +115,7 @@ public class LimitingResourcesComponent extends XulElement { } private static int getWidthPixels(IDatesMapper datesMapper, - LoadPeriod loadPeriod) { + QueueTask loadPeriod) { LocalDate start = loadPeriod.getStart(); LocalDate end = loadPeriod.getEnd(); return datesMapper @@ -128,7 +131,7 @@ public class LimitingResourcesComponent extends XulElement { } private static int getStartPixels(IDatesMapper datesMapper, - LoadPeriod loadPeriod) { + QueueTask loadPeriod) { return datesMapper.toPixels(loadPeriod.getStart().toDateMidnight() .toDate()); } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesLeftPane.java b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesLeftPane.java index 343c0ce46..fc579410e 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesLeftPane.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesLeftPane.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; import org.zkoss.ganttz.data.resourceload.LoadTimeLine; import org.zkoss.ganttz.util.MutableTreeModel; import org.zkoss.zk.ui.Component; @@ -43,13 +44,14 @@ import org.zkoss.zul.api.Tree; public class LimitingResourcesLeftPane extends HtmlMacroComponent { - private MutableTreeModel modelForTree; + private MutableTreeModel modelForTree; private final LimitingResourcesList limitingResourcesList; - public LimitingResourcesLeftPane(MutableTreeModel modelForTree, + public LimitingResourcesLeftPane( + MutableTreeModel treeModel, LimitingResourcesList resourceLoadList) { this.limitingResourcesList = resourceLoadList; - this.modelForTree = modelForTree; + this.modelForTree = treeModel; } @@ -75,7 +77,7 @@ public class LimitingResourcesLeftPane extends HtmlMacroComponent { item.appendChild(row); row.appendChild(cell); cell.appendChild(component); - collapse(line); + // collapse(line); addExpandedListener(item, line); } @@ -85,12 +87,12 @@ public class LimitingResourcesLeftPane extends HtmlMacroComponent { @Override public void onEvent(Event event) throws Exception { OpenEvent openEvent = (OpenEvent) event; - if (openEvent.isOpen()) { - List closed = calculatedClosedItems(item); - expand(line, closed); - } else { - collapse(line); - } +// if (openEvent.isOpen()) { +// List closed = calculatedClosedItems(item); +// expand(line, closed); +// } else { +// collapse(line); +// } } }); } @@ -107,12 +109,14 @@ public class LimitingResourcesLeftPane extends HtmlMacroComponent { }; } - private void collapse(LoadTimeLine line) { + private void collapse(LimitingResourceQueue line) { + // unnecesary limitingResourcesList.collapse(line); } - private void expand(LoadTimeLine line, List closed) { - limitingResourcesList.expand(line, closed); + private void expand(LoadTimeLine line, List closed) { + // unnecesary + // limitingResourcesList.expand(line, closed); } private List calculatedClosedItems(Treeitem item) { diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesList.java b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesList.java index 7a3257ec1..7150213dc 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesList.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesList.java @@ -26,7 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.zkoss.ganttz.data.resourceload.LoadTimeLine; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; import org.zkoss.ganttz.timetracker.TimeTracker; import org.zkoss.ganttz.timetracker.zoom.IZoomLevelChangedListener; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; @@ -45,26 +45,26 @@ public class LimitingResourcesList extends HtmlMacroComponent implements private final IZoomLevelChangedListener zoomListener; - private Map fromTimeLineToComponent = new HashMap(); + private Map fromTimeLineToComponent = new HashMap(); - private final MutableTreeModel timelinesTree; + private final MutableTreeModel timelinesTree; public LimitingResourcesList(TimeTracker timeTracker, - MutableTreeModel timelinesTree) { + MutableTreeModel timelinesTree) { this.timelinesTree = timelinesTree; zoomListener = adjustTimeTrackerSizeListener(); timeTracker.addZoomListener(zoomListener); - LoadTimeLine current = timelinesTree.getRoot(); - List toInsert = new ArrayList(); + LimitingResourceQueue current = timelinesTree.getRoot(); + List toInsert = new ArrayList(); fill(timelinesTree, current, toInsert); insertAsComponents(timeTracker, toInsert); } - private void fill(MutableTreeModel timelinesTree, - LoadTimeLine current, List result) { + private void fill(MutableTreeModel timelinesTree, + LimitingResourceQueue current, List result) { final int length = timelinesTree.getChildCount(current); for (int i = 0; i < length; i++) { - LoadTimeLine child = timelinesTree.getChild(current, i); + LimitingResourceQueue child = timelinesTree.getChild(current, i); result.add(child); fill(timelinesTree, child, result); } @@ -84,45 +84,47 @@ public class LimitingResourcesList extends HtmlMacroComponent implements } private void insertAsComponents(TimeTracker timetracker, - List children) { - for (LoadTimeLine loadTimeLine : children) { + List children) { + for (LimitingResourceQueue LimitingResourceQueue : children) { LimitingResourcesComponent component = LimitingResourcesComponent .create( - timetracker, loadTimeLine); +timetracker, LimitingResourceQueue); appendChild(component); - fromTimeLineToComponent.put(loadTimeLine, component); + fromTimeLineToComponent.put(LimitingResourceQueue, component); } } - public void collapse(LoadTimeLine line) { - for (LoadTimeLine l : line.getAllChildren()) { + public void collapse(LimitingResourceQueue line) { + for (LimitingResourceQueue l : line.getAllChildren()) { getComponentFor(l).detach(); } } - private LimitingResourcesComponent getComponentFor(LoadTimeLine l) { + private LimitingResourcesComponent getComponentFor(LimitingResourceQueue l) { LimitingResourcesComponent resourceLoadComponent = fromTimeLineToComponent .get(l); return resourceLoadComponent; } - public void expand(LoadTimeLine line, List closed) { + public void expand(LimitingResourceQueue line, + List closed) { LimitingResourcesComponent parentComponent = getComponentFor(line); Component nextSibling = parentComponent.getNextSibling(); - List childrenToOpen = getChildrenReverseOrderFor(line); + List childrenToOpen = getChildrenReverseOrderFor(line); childrenToOpen.removeAll(closed); - for (LoadTimeLine loadTimeLine : childrenToOpen) { - LimitingResourcesComponent child = getComponentFor(loadTimeLine); + for (LimitingResourceQueue LimitingResourceQueue : childrenToOpen) { + LimitingResourcesComponent child = getComponentFor(LimitingResourceQueue); insertBefore(child, nextSibling); nextSibling = child; } } - private List getChildrenReverseOrderFor(LoadTimeLine line) { - List childrenOf = line.getAllChildren(); + private List getChildrenReverseOrderFor( + LimitingResourceQueue line) { + List childrenOf = line.getAllChildren(); Collections.reverse(childrenOf); return childrenOf; } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesPanel.java b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesPanel.java index cd9fab257..8aad067f9 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesPanel.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/limitingresources/LimitingResourcesPanel.java @@ -25,7 +25,7 @@ import static org.zkoss.ganttz.i18n.I18nHelper._; import java.util.List; import org.apache.commons.lang.StringUtils; -import org.zkoss.ganttz.data.resourceload.LoadTimeLine; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; import org.zkoss.ganttz.timetracker.TimeTracker; import org.zkoss.ganttz.timetracker.TimeTrackerComponent; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; @@ -61,9 +61,9 @@ public class LimitingResourcesPanel extends HtmlMacroComponent { private LimitingResourcesList limitingResourcesList; - private List groups; + private List groups; - private MutableTreeModel treeModel; + private MutableTreeModel treeModel; private TimeTracker timeTracker; @@ -78,13 +78,13 @@ public class LimitingResourcesPanel extends HtmlMacroComponent { private static final String filterCriterions = _("Filter by criterions"); private boolean filterbyResources = true; - public LimitingResourcesPanel(List groups, + public LimitingResourcesPanel(List groups, TimeTracker timeTracker) { init(groups, timeTracker); } - public void init(List groups, TimeTracker timeTracker) { + public void init(List groups, TimeTracker timeTracker) { this.groups = groups; this.timeTracker = timeTracker; treeModel = createModelForTree(); @@ -190,23 +190,25 @@ public class LimitingResourcesPanel extends HtmlMacroComponent { .retrieve(); } - private MutableTreeModel createModelForTree() { - MutableTreeModel result = MutableTreeModel - .create(LoadTimeLine.class); - for (LoadTimeLine loadTimeLine : this.groups) { - result.addToRoot(loadTimeLine); - result = addNodes(result, loadTimeLine); + private MutableTreeModel createModelForTree() { + MutableTreeModel result = MutableTreeModel + .create(LimitingResourceQueue.class); + for (LimitingResourceQueue LimitingResourceQueue : this.groups) { + result.addToRoot(LimitingResourceQueue); + result = addNodes(result, LimitingResourceQueue); } return result; } - private MutableTreeModel addNodes( - MutableTreeModel tree, LoadTimeLine parent) { + private MutableTreeModel addNodes( + MutableTreeModel tree, + LimitingResourceQueue parent) { if (!parent.getChildren().isEmpty()) { tree.add(parent, parent.getChildren()); - for (LoadTimeLine loadTimeLine : parent.getChildren()) { - tree = addNodes(tree, loadTimeLine); + for (LimitingResourceQueue LimitingResourceQueue : parent + .getChildren()) { + tree = addNodes(tree, LimitingResourceQueue); } } return tree; diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java index 2d60d856f..ba550463a 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java @@ -24,7 +24,7 @@ import java.util.List; import org.navalplanner.business.orders.entities.Order; import org.navalplanner.business.planner.entities.TaskElement; -import org.zkoss.ganttz.data.resourceload.LoadTimeLine; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; import org.zkoss.ganttz.util.Interval; @@ -34,7 +34,7 @@ public interface ILimitingResourceQueueModel { void initGlobalView(Order filterBy, boolean filterByResources); - List getLoadTimeLines(); + List getLimitingResourceQueues(); Interval getViewInterval(); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java index 4936c6154..f8ab2b4d7 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java @@ -28,7 +28,9 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.Map.Entry; @@ -63,7 +65,8 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.zkoss.ganttz.data.resourceload.LoadTimeLine; +import org.zkoss.ganttz.data.limitingresource.LimitingResourceQueue; +import org.zkoss.ganttz.data.limitingresource.QueueTask; import org.zkoss.ganttz.data.resourceload.TimeLineRole; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; import org.zkoss.ganttz.util.Interval; @@ -93,7 +96,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { @Autowired private IOrderAuthorizationDAO orderAuthorizationDAO; - private List loadTimeLines; + private List limitingResourceQueues; private Interval viewInterval; private Order filterBy; @@ -148,9 +151,10 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { } private void doGlobalView() { - loadTimeLines = calculateLoadTimeLines(); - if (!loadTimeLines.isEmpty()) { - viewInterval = LoadTimeLine.getIntervalFrom(loadTimeLines); + limitingResourceQueues = calculateLimitingResourceQueues(); + if (!limitingResourceQueues.isEmpty()) { + viewInterval = LimitingResourceQueue + .getIntervalFrom(limitingResourceQueues); } else { viewInterval = new Interval(new Date(), plusFiveYears(new Date())); } @@ -163,9 +167,9 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return calendar.getTime(); } - private List calculateLoadTimeLines() { - List result = new ArrayList(); - result.addAll(groupsFor(genericAllocationsByCriterion())); + private List calculateLimitingResourceQueues() { + List result = new ArrayList(); + result.addAll(groupsFor(resourcesToShow())); return result; } @@ -226,37 +230,72 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return new TimeLineRole(entity); } - /** - * @param genericAllocationsByCriterion - * @return - */ - private List groupsFor( - Map> genericAllocationsByCriterion) { - List result = new ArrayList(); - List criterions = Criterion - .sortByName(genericAllocationsByCriterion.keySet()); - for (Criterion criterion : criterions) { - List allocations = ResourceAllocation - .sortedByStartDate(genericAllocationsByCriterion - .get(criterion)); - TimeLineRole role = getCurrentTimeLineRole(criterion); + private List groupsFor(List allResources) { + List result = new ArrayList(); + for (Resource resource : allResources) { + LimitingResourceQueue group = buildGroup(resource); + if (!group.isEmpty()) { + result.add(group); + } } return result; } - private List buildTimeLinesForOrder(Criterion criterion, - List> allocations) { - List result = new ArrayList(); - result.addAll(buildTimeLinesForEachTask(criterion, allocations)); + private LimitingResourceQueue buildGroup(Resource resource) { + List> sortedByStartDate = ResourceAllocation + .sortedByStartDate(resourceAllocationDAO + .findAllocationsRelatedTo(resource)); + TimeLineRole role = getCurrentTimeLineRole(resource); + LimitingResourceQueue result = new LimitingResourceQueue(buildTimeLine( + resource, resource.getShortDescription(), sortedByStartDate, + "resource", role), + buildSecondLevel(resource, sortedByStartDate)); return result; } - private List buildTimeLinesForEachTask(Criterion criterion, + private LimitingResourceQueue buildTimeLine(Resource resource, String name, + List> sortedByStartDate, + String type, TimeLineRole role) { + return new LimitingResourceQueue(name, PeriodsBuilder.build( + QueueTaskGenerator.onResource(resource), sortedByStartDate), + type, role); + } + + private List buildSecondLevel(Resource resource, + List> sortedByStartDate) { + List result = new ArrayList(); + Map>> byOrder = byOrder(sortedByStartDate); + + if (filter()) { + // build time lines for current order + result.addAll(buildTimeLinesForOrder(resource, byOrder + .get(filterBy))); + byOrder.remove(filterBy); + // build time lines for other orders + LimitingResourceQueue lineOthersOrders = buildTimeLinesForOtherOrders( + resource, byOrder); + if (lineOthersOrders != null) { + result.add(lineOthersOrders); + } + } else { + // result.addAll(buildTimeLinesGroupForOrder(resource, byOrder)); + } + return result; + } + + private LimitingResourceQueue buildTimeLinesForOtherOrders( + Resource resource, Map>> byOrder) { + // TODO Auto-generated method stub + return null; + } + + private List buildTimeLinesForEachTask( + Criterion criterion, List> allocations) { Map>> byTask = ResourceAllocation .byTask(allocations); - List secondLevel = new ArrayList(); + List secondLevel = new ArrayList(); for (Entry>> entry : byTask.entrySet()) { Task task = entry.getKey(); Set criterions = task.getCriterions(); @@ -271,23 +310,6 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return secondLevel; } - private List buildTimeLinesForEachResource( - Criterion criterion, List allocations, - TimeLineRole role) { - Map> byResource = GenericResourceAllocation - .byResource(allocations); - - List secondLevel = new ArrayList(); - for (Entry> entry : byResource - .entrySet()) { - Resource resource = entry.getKey(); - List resourceAllocations = entry - .getValue(); - String descriptionTimeLine = getDescriptionResourceWithCriterions(resource); - } - return secondLevel; - } - private String getDescriptionResourceWithCriterions(Resource resource) { Set criterionSatisfactions = resource .getCriterionSatisfactions(); @@ -341,13 +363,15 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return result; } - private List buildTimeLinesForOrder(Resource resource, + private List buildTimeLinesForOrder( + Resource resource, List> sortedByStartDate) { - List result = new ArrayList(); - result.addAll(buildTimeLinesForEachTask(resource, - onlySpecific(sortedByStartDate))); - result.addAll(buildTimeLinesForEachCriterion(resource, - onlyGeneric(sortedByStartDate))); + List result = new ArrayList(); + // Add all time lines + // result.addAll(buildTimeLinesForEachTask(resource, + // onlySpecific(sortedByStartDate))); + // result.addAll(buildTimeLinesForEachCriterion(resource, + // onlyGeneric(sortedByStartDate))); return result; } @@ -363,12 +387,12 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { sortedByStartDate); } - private List buildTimeLinesForEachCriterion( + private List buildTimeLinesForEachCriterion( Resource resource, List sortdByStartDate) { Map, List> byCriterions = GenericResourceAllocation .byCriterions(sortdByStartDate); - List result = new ArrayList(); + List result = new ArrayList(); for (Entry, List> entry : byCriterions .entrySet()) { @@ -388,7 +412,8 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return result; } - private List buildTimeLinesForEachTask(Resource resource, + private List buildTimeLinesForEachTask( + Resource resource, List sortedByStartDate) { List> listOnlySpecific = new ArrayList>( @@ -396,7 +421,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { Map>> byTask = ResourceAllocation .byTask(listOnlySpecific); - List secondLevel = new ArrayList(); + List secondLevel = new ArrayList(); for (Entry>> entry : byTask.entrySet()) { Task task = entry.getKey(); TimeLineRole role = getCurrentTimeLineRole(task); @@ -425,8 +450,8 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { @Override - public List getLoadTimeLines() { - return loadTimeLines; + public List getLimitingResourceQueues() { + return limitingResourceQueues; } @Override @@ -440,4 +465,141 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { .getStart()), new LocalDate(interval.getFinish())); } + +} + +class PeriodsBuilder { + + private final List> sortedByStartDate; + + private final List loadPeriodsGenerators = new LinkedList(); + + private final QueueTaskGeneratorFactory factory; + + private PeriodsBuilder(QueueTaskGeneratorFactory factory, + List> sortedByStartDate) { + this.factory = factory; + this.sortedByStartDate = sortedByStartDate; + } + + public static List build(QueueTaskGeneratorFactory factory, + List> sortedByStartDate) { + return new PeriodsBuilder(factory, sortedByStartDate).buildPeriods(); + } + + private List buildPeriods() { + for (ResourceAllocation resourceAllocation : sortedByStartDate) { + loadPeriodsGenerators.add(factory.create(resourceAllocation)); + } + joinPeriodGenerators(); + return toGenerators(loadPeriodsGenerators); + } + + private List toGenerators(List generators) { + List result = new ArrayList(); + for (QueueTaskGenerator queueTaskGenerator : generators) { + result.add(queueTaskGenerator.build()); + } + return result; + } + + private void joinPeriodGenerators() { + ListIterator iterator = loadPeriodsGenerators + .listIterator(); + while (iterator.hasNext()) { + final QueueTaskGenerator current = findNextOneOverlapping(iterator); + if (current != null) { + rewind(iterator, current); + iterator.remove(); + QueueTaskGenerator next = iterator.next(); + iterator.remove(); + List generated = current.join(next); + final QueueTaskGenerator positionToComeBack = generated.get(0); + final List remaining = loadPeriodsGenerators + .subList(iterator.nextIndex(), loadPeriodsGenerators + .size()); + List generatorsSortedByStartDate = mergeListsKeepingByStartSortOrder( + generated, remaining); + final int takenFromRemaining = generatorsSortedByStartDate + .size() + - generated.size(); + removeNextElements(iterator, takenFromRemaining); + addAtCurrentPosition(iterator, generatorsSortedByStartDate); + rewind(iterator, positionToComeBack); + } + } + } + + private QueueTaskGenerator findNextOneOverlapping( + ListIterator iterator) { + while (iterator.hasNext()) { + QueueTaskGenerator current = iterator.next(); + if (!iterator.hasNext()) { + return null; + } + QueueTaskGenerator next = peekNext(iterator); + if (current.overlaps(next)) { + return current; + } + } + return null; + } + + private void addAtCurrentPosition( + ListIterator iterator, + List sortedByStartDate) { + for (QueueTaskGenerator l : sortedByStartDate) { + iterator.add(l); + } + } + + private void removeNextElements(ListIterator iterator, + final int elementsNumber) { + for (int i = 0; i < elementsNumber; i++) { + iterator.next(); + iterator.remove(); + } + } + + private void rewind(ListIterator iterator, + QueueTaskGenerator nextOne) { + while (peekNext(iterator) != nextOne) { + iterator.previous(); + } + } + + private List mergeListsKeepingByStartSortOrder( + List joined, List remaining) { + List result = new ArrayList(); + ListIterator joinedIterator = joined.listIterator(); + ListIterator remainingIterator = remaining + .listIterator(); + while (joinedIterator.hasNext() && remainingIterator.hasNext()) { + QueueTaskGenerator fromJoined = peekNext(joinedIterator); + QueueTaskGenerator fromRemaining = peekNext(remainingIterator); + if (fromJoined.getStart().compareTo(fromRemaining.getStart()) <= 0) { + result.add(fromJoined); + joinedIterator.next(); + } else { + result.add(fromRemaining); + remainingIterator.next(); + } + } + if (joinedIterator.hasNext()) { + result.addAll(joined.subList(joinedIterator.nextIndex(), joined + .size())); + } + return result; + } + + private QueueTaskGenerator peekNext( + ListIterator iterator) { + if (!iterator.hasNext()) { + return null; + } + QueueTaskGenerator result = iterator.next(); + iterator.previous(); + return result; + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java index e81303cb1..925738988 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java @@ -139,7 +139,7 @@ public class LimitingResourcesController implements Composer { private LimitingResourcesPanel buildLimitingResourcesPanel() { return new LimitingResourcesPanel(limitingResourceQueueModel - .getLoadTimeLines(), + .getLimitingResourceQueues(), timeTracker); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueueTaskGenerator.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueueTaskGenerator.java new file mode 100644 index 000000000..7247617c1 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueueTaskGenerator.java @@ -0,0 +1,365 @@ +/* + * This file is part of NavalPlan + * + * 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.limitingresources; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.LocalDate; +import org.navalplanner.business.planner.entities.DayAssignment; +import org.navalplanner.business.planner.entities.GenericResourceAllocation; +import org.navalplanner.business.planner.entities.ResourceAllocation; +import org.navalplanner.business.planner.entities.SpecificDayAssignment; +import org.navalplanner.business.resources.daos.IResourceDAO; +import org.navalplanner.business.resources.entities.Criterion; +import org.navalplanner.business.resources.entities.CriterionCompounder; +import org.navalplanner.business.resources.entities.ICriterion; +import org.navalplanner.business.resources.entities.Resource; +import org.zkoss.ganttz.data.limitingresource.QueueTask; +import org.zkoss.ganttz.data.resourceload.LoadLevel; + +interface QueueTaskGeneratorFactory { + QueueTaskGenerator create(ResourceAllocation allocation); +} + + +abstract class QueueTaskGenerator { + + private static final Log LOG = LogFactory.getLog(QueueTaskGenerator.class); + + public static QueueTaskGeneratorFactory onResource(Resource resource) { + return new OnResourceFactory(resource); + } + + public static QueueTaskGeneratorFactory onResourceSatisfying( + Resource resource, Collection criterions) { + return new OnResourceFactory(resource, criterions); + } + + private static class OnResourceFactory implements + QueueTaskGeneratorFactory { + + private final Resource resource; + + private final ICriterion criterion; + + public OnResourceFactory(Resource resource) { + this(resource, Collections. emptyList()); + } + + public OnResourceFactory(Resource resource, + Collection criterionsToSatisfy) { + Validate.notNull(resource); + this.resource = resource; + this.criterion = CriterionCompounder.buildAnd(criterionsToSatisfy) + .getResult(); + } + + @Override + public QueueTaskGenerator create(ResourceAllocation allocation) { + return new QueueTaskGeneratorOnResource(resource, allocation, + criterion); + } + + } + + public static QueueTaskGeneratorFactory onCriterion( + final Criterion criterion, final IResourceDAO resourcesDAO) { + return new QueueTaskGeneratorFactory() { + + @Override + public QueueTaskGenerator create(ResourceAllocation allocation) { + return new QueueTaskGeneratorOnCriterion(criterion, + allocation, findResources(criterion, resourcesDAO)); + } + + private List findResources(final Criterion criterion, + final IResourceDAO resourcesDAO) { + return resourcesDAO + .findSatisfyingCriterionsAtSomePoint(Collections + .singletonList(criterion)); + } + }; + } + + protected final LocalDate start; + protected final LocalDate end; + + private List> allocationsOnInterval = new ArrayList>(); + + protected QueueTaskGenerator(LocalDate start, + LocalDate end, List> allocationsOnInterval) { + Validate.notNull(start); + Validate.notNull(end); + Validate.notNull(allocationsOnInterval); + this.start = start; + this.end = end; + this.allocationsOnInterval = ResourceAllocation + .getSatisfied(allocationsOnInterval); + } + + public List join(QueueTaskGenerator next) { + if (!overlaps(next)) { + return stripEmpty(this, next); + } + if (isIncluded(next)) { + return stripEmpty(this.until(next.start), intersect(next), this + .from(next.end)); + } + assert overlaps(next) && !isIncluded(next); + return stripEmpty(this.until(next.start), intersect(next), next + .from(end)); + } + + protected List> getAllocationsOnInterval() { + return allocationsOnInterval; + } + + private List stripEmpty( + QueueTaskGenerator... generators) { + List result = new ArrayList(); + for (QueueTaskGenerator loadPeriodGenerator : generators) { + if (!loadPeriodGenerator.isEmpty()) { + result.add(loadPeriodGenerator); + } + } + return result; + } + + private boolean isEmpty() { + return start.equals(end); + } + + protected abstract QueueTaskGenerator create(LocalDate start, + LocalDate end, List> allocationsOnInterval); + + private QueueTaskGenerator intersect(QueueTaskGenerator other) { + return create(max(this.start, other.start), + min(this.end, other.end), plusAllocations(other)); + } + + private static LocalDate max(LocalDate l1, LocalDate l2) { + return l1.compareTo(l2) < 0 ? l2 : l1; + } + + private static LocalDate min(LocalDate l1, LocalDate l2) { + return l1.compareTo(l2) < 0 ? l1 : l2; + } + + private List> plusAllocations( + QueueTaskGenerator other) { + List> result = new ArrayList>(); + result.addAll(allocationsOnInterval); + result.addAll(other.allocationsOnInterval); + return result; + } + + private QueueTaskGenerator from(LocalDate newStart) { + return create(newStart, end, + allocationsOnInterval); + } + + private QueueTaskGenerator until(LocalDate newEnd) { + return create(start, newEnd, + allocationsOnInterval); + } + + boolean overlaps(QueueTaskGenerator other) { + return (start.compareTo(other.end) < 0 && other.start + .compareTo(this.end) < 0); + } + + private boolean isIncluded(QueueTaskGenerator other) { + return other.start.compareTo(start) >= 0 + && other.end.compareTo(end) <= 0; + } + + public QueueTask build() { + return new QueueTask(start, end, getTotalWorkHours(), + getHoursAssigned(), new LoadLevel( + calculateLoadPercentage())); + } + + protected abstract int getTotalWorkHours(); + + private int calculateLoadPercentage() { + final int totalResourceWorkHours = getTotalWorkHours(); + int assigned = getHoursAssigned(); + if (totalResourceWorkHours == 0) { + return assigned == 0 ? 0 : Integer.MAX_VALUE; + } + double proportion = assigned / (double) totalResourceWorkHours; + return new BigDecimal(proportion).scaleByPowerOfTen(2).intValue(); + } + + protected abstract int getHoursAssigned(); + + protected final int sumAllocations() { + int sum = 0; + for (ResourceAllocation resourceAllocation : allocationsOnInterval) { + sum += getAssignedHoursFor(resourceAllocation); + } + return sum; + } + + protected abstract int getAssignedHoursFor( + ResourceAllocation resourceAllocation); + + public LocalDate getStart() { + return start; + } + + public LocalDate getEnd() { + return end; + } +} + +class QueueTaskGeneratorOnResource extends QueueTaskGenerator { + + private Resource resource; + + private final ICriterion criterion; + + QueueTaskGeneratorOnResource(Resource resource, LocalDate start, + LocalDate end, List> allocationsOnInterval, + ICriterion criterion) { + super(start, end, allocationsOnInterval); + this.resource = resource; + this.criterion = criterion; + } + + QueueTaskGeneratorOnResource(Resource resource, + ResourceAllocation initial, ICriterion criterion) { + super(initial.getStartDate(), initial.getEndDate(), Arrays.> asList(initial)); + this.resource = resource; + this.criterion = criterion; + } + + @Override + protected QueueTaskGenerator create(LocalDate start, LocalDate end, + List> allocationsOnInterval) { + return new QueueTaskGeneratorOnResource(resource, start, end, + allocationsOnInterval, criterion); + } + + @Override + protected int getTotalWorkHours() { + return resource.getTotalWorkHours(start, end, criterion); + } + + @Override + protected int getAssignedHoursFor(ResourceAllocation resourceAllocation) { + return resourceAllocation.getAssignedHours(resource, start, end); + } + + @Override + protected int getHoursAssigned() { + return sumAllocations(); + } + +} + +class QueueTaskGeneratorOnCriterion extends QueueTaskGenerator { + + private final Criterion criterion; + private final List resourcesSatisfyingCriterionAtSomePoint; + + public QueueTaskGeneratorOnCriterion(Criterion criterion, + ResourceAllocation allocation, + List resourcesSatisfyingCriterionAtSomePoint) { + this(criterion, allocation.getStartDate(), allocation.getEndDate(), + Arrays.> asList(allocation), + resourcesSatisfyingCriterionAtSomePoint); + } + + public QueueTaskGeneratorOnCriterion(Criterion criterion, + LocalDate startDate, LocalDate endDate, + List> allocations, + List resourcesSatisfyingCriterionAtSomePoint) { + super(startDate, endDate, allocations); + this.criterion = criterion; + this.resourcesSatisfyingCriterionAtSomePoint = resourcesSatisfyingCriterionAtSomePoint; + } + + @Override + protected QueueTaskGenerator create(LocalDate start, LocalDate end, + List> allocationsOnInterval) { + QueueTaskGeneratorOnCriterion result = new QueueTaskGeneratorOnCriterion( + criterion, start, end, allocationsOnInterval, + resourcesSatisfyingCriterionAtSomePoint); + result.specificByResourceCached = specificByResourceCached; + return result; + } + + private List genericAllocationsOnInterval() { + return ResourceAllocation.getOfType(GenericResourceAllocation.class, + getAllocationsOnInterval()); + } + + @Override + protected int getAssignedHoursFor(ResourceAllocation resourceAllocation) { + return resourceAllocation.getAssignedHours(start, end); + } + + @Override + protected int getTotalWorkHours() { + int sum = 0; + for (Resource resource : resourcesSatisfyingCriterionAtSomePoint) { + sum += resource.getTotalWorkHours(start, end, criterion); + } + return sum; + } + + @Override + protected int getHoursAssigned() { + return sumAllocations(); + } + + private Map> specificByResourceCached = new HashMap>(); + + private List getSpecificOrderedAssignmentsFor( + Resource resource) { + if (!specificByResourceCached.containsKey(resource)) { + specificByResourceCached.put(resource, DayAssignment + .specific(DayAssignment.orderedByDay(resource + .getAssignments()))); + } + return specificByResourceCached.get(resource); + } + + private int sum(List specific) { + int result = 0; + for (SpecificDayAssignment s : specific) { + result += s.getHours(); + } + return result; + } + +}