diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java index 387896d5d..8e37fb1e3 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/PredefinedDatabaseSnapshots.java @@ -27,6 +27,10 @@ import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.navalplanner.business.calendars.entities.CalendarAvailability; +import org.navalplanner.business.calendars.entities.CalendarData; +import org.navalplanner.business.calendars.entities.CalendarException; import org.navalplanner.business.common.AdHocTransactionService; import org.navalplanner.business.common.IAdHocTransactionService; import org.navalplanner.business.costcategories.daos.ICostCategoryDAO; @@ -39,6 +43,13 @@ import org.navalplanner.business.labels.entities.Label; import org.navalplanner.business.labels.entities.LabelType; import org.navalplanner.business.orders.daos.IOrderDAO; import org.navalplanner.business.orders.entities.Order; +import org.navalplanner.business.planner.daos.IDayAssignmentDAO; +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.ResourceLoadChartData; +import org.navalplanner.business.planner.entities.SpecificResourceAllocation; +import org.navalplanner.business.planner.entities.TaskElement; import org.navalplanner.business.resources.daos.ICriterionDAO; import org.navalplanner.business.resources.daos.ICriterionTypeDAO; import org.navalplanner.business.resources.daos.IResourceDAO; @@ -49,6 +60,7 @@ import org.navalplanner.business.resources.entities.Machine; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.VirtualWorker; import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.business.scenarios.IScenarioManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -125,6 +137,13 @@ public class PredefinedDatabaseSnapshots { return ordersCodes.getValue(); } + private IAutoUpdatedSnapshot + resourceLoadChartData; + + public ResourceLoadChartData snapshotResourceLoadChartData() { + return resourceLoadChartData.getValue(); + } + private boolean snapshotsRegistered = false; public void registerSnapshots() { @@ -153,6 +172,11 @@ public class PredefinedDatabaseSnapshots { calculateCustomerReferences(), Order.class); ordersCodes = snapshot("order codes", calculateOrdersCodes(), Order.class); + resourceLoadChartData = snapshot("resource load grouped by date", + calculateResourceLoadChartData(), + CalendarAvailability.class, CalendarException.class, + CalendarData.class, TaskElement.class, SpecificResourceAllocation.class, + GenericResourceAllocation.class, ResourceAllocation.class); } private IAutoUpdatedSnapshot snapshot(String name, @@ -311,4 +335,25 @@ public class PredefinedDatabaseSnapshots { }; } + @Autowired + private IDayAssignmentDAO dayAssignmentDAO; + + @Autowired + private IScenarioManager scenarioManager; + + private Callable calculateResourceLoadChartData() { + return new Callable() { + @Override + public ResourceLoadChartData call() + throws Exception { + + List dayAssignments = dayAssignmentDAO.getAllFor( + scenarioManager.getCurrent(), null, null); + List resources = resourceDAO.list(Resource.class); + return new ResourceLoadChartData(dayAssignments, resources); + + } + }; + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java new file mode 100644 index 000000000..550049828 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceLoadChartData.java @@ -0,0 +1,248 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009-2010 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.business.planner.entities; + +import static org.navalplanner.business.workingday.EffortDuration.min; +import static org.navalplanner.business.workingday.EffortDuration.zero; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.Map.Entry; + +import org.joda.time.LocalDate; +import org.navalplanner.business.calendars.entities.BaseCalendar; +import org.navalplanner.business.calendars.entities.ICalendar; +import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay; +import org.navalplanner.business.resources.entities.Resource; +import org.navalplanner.business.workingday.EffortDuration; +import org.navalplanner.business.workingday.IntraDayDate.PartialDay; + +/** + * This class groups the calculation of the three values needed for the + * chart of the company global resource load. The purpose of the class is + * having these data pre-calculated to prevent heavy algorithms being + * run each time the chart is shown. + * @see PredefinedDatabaseSnapshots + * @author Jacobo Aragunde Pérez + * + */ +public class ResourceLoadChartData { + + private SortedMap load; + + private SortedMap overload; + + private SortedMap availability; + + public ResourceLoadChartData(List dayAssignments, List resources) { + SortedMap> map = + groupDurationsByDayAndResource(dayAssignments); + this.load = calculateResourceLoadPerDate(map); + this.overload = calculateResourceOverloadPerDate(map); + if(load.keySet().isEmpty()) { + this.availability = new TreeMap(); + } + else { + this.availability = calculateAvailabilityDurationByDay( + resources, load.firstKey(), load.lastKey()); + } + + for (LocalDate day : this.overload.keySet()) { + EffortDuration overloadDuration = this.overload + .get(day); + EffortDuration maxDuration = this.availability.get(day); + this.overload.put(day, + overloadDuration.plus(maxDuration)); + } + } + + public SortedMap getLoad() { + return load; + } + + public SortedMap getOverload() { + return overload; + } + + public SortedMap getAvailability() { + return availability; + } + + private SortedMap> groupDurationsByDayAndResource( + List dayAssignments) { + SortedMap> map = + new TreeMap>(); + + for (DayAssignment dayAssignment : dayAssignments) { + final LocalDate day = dayAssignment.getDay(); + final EffortDuration dayAssignmentDuration = dayAssignment + .getDuration(); + Resource resource = dayAssignment.getResource(); + if (map.get(day) == null) { + map.put(day, new HashMap()); + } + Map forDay = map.get(day); + EffortDuration previousDuration = forDay.get(resource); + previousDuration = previousDuration != null ? previousDuration + : EffortDuration.zero(); + forDay.put(dayAssignment.getResource(), + previousDuration.plus(dayAssignmentDuration)); + } + return map; + } + + private SortedMap calculateResourceLoadPerDate( + SortedMap> durationsGrouped) { + SortedMap map = new TreeMap(); + + for (LocalDate date : durationsGrouped.keySet()) { + EffortDuration result = zero(); + PartialDay day = PartialDay.wholeDay(date); + for (Resource resource : durationsGrouped.get(date).keySet()) { + ICalendar calendar = resource.getCalendarOrDefault(); + EffortDuration workableTime = calendar.getCapacityOn(day); + EffortDuration assignedDuration = durationsGrouped.get( + day.getDate()).get(resource); + result = result.plus(min(assignedDuration, workableTime)); + } + + map.put(date, result); + } + return map; + } + + protected abstract class EffortByDayCalculator { + public SortedMap calculate( + Collection elements) { + SortedMap result = new TreeMap(); + if (elements.isEmpty()) { + return result; + } + for (T element : elements) { + if (included(element)) { + EffortDuration duration = getDurationFor(element); + LocalDate day = getDayFor(element); + EffortDuration previous = result.get(day); + previous = previous == null ? zero() : previous; + result.put(day, previous.plus(duration)); + } + } + return result; + } + + protected abstract LocalDate getDayFor(T element); + + protected abstract EffortDuration getDurationFor(T element); + + protected boolean included(T each) { + return true; + } + } + + private SortedMap calculateResourceOverloadPerDate( + SortedMap> dayAssignmentGrouped) { + return new EffortByDayCalculator>>() { + + @Override + protected LocalDate getDayFor( + Entry> element) { + return element.getKey(); + } + + @Override + protected EffortDuration getDurationFor( + Entry> element) { + EffortDuration result = zero(); + PartialDay day = PartialDay.wholeDay(element.getKey()); + for (Entry each : element.getValue() + .entrySet()) { + EffortDuration overlad = getOverloadAt(day, + each.getKey(), + each.getValue()); + result = result.plus(overlad); + } + return result; + } + + private EffortDuration getOverloadAt(PartialDay day, + Resource resource, EffortDuration assignedDuration) { + ICalendar calendar = resource.getCalendarOrDefault(); + EffortDuration workableDuration = calendar + .getCapacityOn(day); + if (assignedDuration.compareTo(workableDuration) > 0) { + return assignedDuration.minus(workableDuration); + } + return zero(); + } + }.calculate(dayAssignmentGrouped.entrySet()); + } + + private SortedMap calculateAvailabilityDurationByDay( + final List resources, LocalDate start, LocalDate finish) { + return new EffortByDayCalculator>>() { + + @Override + protected LocalDate getDayFor( + Entry> element) { + return element.getKey(); + } + + @Override + protected EffortDuration getDurationFor( + Entry> element) { + LocalDate day = element.getKey(); + return sumCalendarCapacitiesForDay(resources, day); + } + + }.calculate(getResourcesByDateBetween(resources, start, finish)); + } + + protected static EffortDuration sumCalendarCapacitiesForDay( + Collection resources, LocalDate day) { + PartialDay wholeDay = PartialDay.wholeDay(day); + EffortDuration sum = zero(); + for (Resource resource : resources) { + sum = sum.plus(calendarCapacityFor(resource, + wholeDay)); + } + return sum; + } + + protected static EffortDuration calendarCapacityFor(Resource resource, + PartialDay day) { + return resource.getCalendarOrDefault().getCapacityOn(day); + } + + private Set>> getResourcesByDateBetween( + List resources, LocalDate start, LocalDate finish) { + Map> result = new HashMap>(); + for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date + .plusDays(1)) { + result.put(date, resources); + } + return result.entrySet(); + } +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java index 02765449d..37963b32c 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/planner/company/CompanyPlanningModel.java @@ -20,8 +20,6 @@ package org.navalplanner.web.planner.company; -import static org.navalplanner.business.workingday.EffortDuration.min; -import static org.navalplanner.business.workingday.EffortDuration.zero; import static org.navalplanner.web.I18nHelper._; import static org.navalplanner.web.resourceload.ResourceLoadModel.asDate; @@ -36,30 +34,25 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.joda.time.LocalDate; -import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.common.IAdHocTransactionService; import org.navalplanner.business.common.IOnTransaction; import org.navalplanner.business.common.daos.IConfigurationDAO; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.hibernate.notification.PredefinedDatabaseSnapshots; import org.navalplanner.business.orders.daos.IOrderDAO; import org.navalplanner.business.orders.entities.Order; import org.navalplanner.business.orders.entities.OrderStatusEnum; -import org.navalplanner.business.planner.daos.IDayAssignmentDAO; import org.navalplanner.business.planner.daos.ITaskElementDAO; -import org.navalplanner.business.planner.entities.DayAssignment; import org.navalplanner.business.planner.entities.ICostCalculator; import org.navalplanner.business.planner.entities.Task; import org.navalplanner.business.planner.entities.TaskElement; import org.navalplanner.business.planner.entities.TaskGroup; import org.navalplanner.business.planner.entities.TaskMilestone; -import org.navalplanner.business.resources.daos.IResourceDAO; -import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.scenarios.IScenarioManager; import org.navalplanner.business.scenarios.entities.Scenario; import org.navalplanner.business.templates.entities.OrderTemplate; @@ -140,12 +133,6 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { @Autowired private IOrderDAO orderDAO; - @Autowired - private IResourceDAO resourceDAO; - - @Autowired - private IDayAssignmentDAO dayAssignmentDAO; - @Autowired private ITaskElementDAO taskElementDAO; @@ -177,6 +164,9 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { private Scenario currentScenario; + @Autowired + private PredefinedDatabaseSnapshots databaseSnapshots; + private LocalDate filterStartDate; private LocalDate filterFinishDate; private static final EnumSet STATUS_VISUALIZED = EnumSet @@ -817,129 +807,19 @@ public abstract class CompanyPlanningModel implements ICompanyPlanningModel { private SortedMap getLoad(LocalDate start, LocalDate finish) { - List dayAssignments = dayAssignmentDAO.getAllFor( - currentScenario, start, finish); - SortedMap> durationsGrouped = groupDurationsByDayAndResource(dayAssignments); - return calculateHoursAdditionByDayWithoutOverload(durationsGrouped); + return groupAsNeededByZoom(databaseSnapshots. + snapshotResourceLoadChartData().getLoad().subMap(start, finish)); } private SortedMap getOverload( LocalDate start, LocalDate finish) { - List dayAssignments = dayAssignmentDAO.getAllFor( - currentScenario, start, finish); - - SortedMap> dayAssignmentGrouped = groupDurationsByDayAndResource(dayAssignments); - SortedMap mapDayAssignments = calculateHoursAdditionByDayJustOverload(dayAssignmentGrouped); - SortedMap mapMaxAvailability = calculateAvailabilityDurationByDay( - resourceDAO.list(Resource.class), start, finish); - - for (LocalDate day : mapDayAssignments.keySet()) { - if (day.compareTo(new LocalDate(start)) >= 0 - && day.compareTo(new LocalDate(finish)) <= 0) { - EffortDuration overloadDuration = mapDayAssignments - .get(day); - EffortDuration maxDuration = mapMaxAvailability.get(day); - mapDayAssignments.put(day, - overloadDuration.plus(maxDuration)); - } - } - - return mapDayAssignments; - } - - private SortedMap calculateHoursAdditionByDayWithoutOverload( - SortedMap> durationsGrouped) { - SortedMap map = new TreeMap(); - - for (LocalDate date : durationsGrouped.keySet()) { - EffortDuration result = zero(); - PartialDay day = PartialDay.wholeDay(date); - for (Resource resource : durationsGrouped.get(date).keySet()) { - ICalendar calendar = resource.getCalendarOrDefault(); - EffortDuration workableTime = calendar.getCapacityOn(day); - EffortDuration assignedDuration = durationsGrouped.get( - day.getDate()).get(resource); - result = result.plus(min(assignedDuration, workableTime)); - } - - map.put(date, result); - } - return groupAsNeededByZoom(map); - } - - private SortedMap calculateHoursAdditionByDayJustOverload( - SortedMap> dayAssignmentGrouped) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - EffortDuration result = zero(); - PartialDay day = PartialDay.wholeDay(element.getKey()); - for (Entry each : element.getValue() - .entrySet()) { - EffortDuration overlad = getOverloadAt(day, - each.getKey(), - each.getValue()); - result = result.plus(overlad); - } - return result; - } - - private EffortDuration getOverloadAt(PartialDay day, - Resource resource, EffortDuration assignedDuration) { - ICalendar calendar = resource.getCalendarOrDefault(); - EffortDuration workableDuration = calendar - .getCapacityOn(day); - if (assignedDuration.compareTo(workableDuration) > 0) { - return assignedDuration.minus(workableDuration); - } - return zero(); - } - }.calculate(dayAssignmentGrouped.entrySet()); + return groupAsNeededByZoom( + databaseSnapshots.snapshotResourceLoadChartData().getOverload().subMap(start, finish)); } private SortedMap getCalendarMaximumAvailability( LocalDate start, LocalDate finish) { - return calculateAvailabilityDurationByDay( - resourceDAO.list(Resource.class), start, finish); - } - - private SortedMap calculateAvailabilityDurationByDay( - List resources, LocalDate start, LocalDate finish) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - LocalDate day = element.getKey(); - List resources = element.getValue(); - return sumCalendarCapacitiesForDay(resources, day); - } - - }.calculate(getResourcesByDateBetween(resources, start, finish)); - } - - private Set>> getResourcesByDateBetween( - List resources, LocalDate start, LocalDate finish) { - Map> result = new HashMap>(); - for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date - .plusDays(1)) { - result.put(date, resources); - } - return result.entrySet(); + return databaseSnapshots.snapshotResourceLoadChartData().getAvailability().subMap(start, finish); } }