/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.planner.chart; import static org.libreplan.business.planner.chart.ContiguousDaysLine.compound; import static org.libreplan.business.planner.chart.ContiguousDaysLine.sum; import static org.libreplan.business.planner.chart.ContiguousDaysLine.toSortedMap; import static org.libreplan.business.workingday.EffortDuration.min; import java.util.Collection; 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 org.joda.time.LocalDate; import org.libreplan.business.hibernate.notification.PredefinedDatabaseSnapshots; import org.libreplan.business.planner.chart.ContiguousDaysLine.IValueTransformer; import org.libreplan.business.planner.entities.DayAssignment; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration.IEffortFrom; import org.libreplan.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<jaragunde@igalia.com> */ public class ResourceLoadChartData implements ILoadChartData { private SortedMap<LocalDate, EffortDuration> load; private SortedMap<LocalDate, EffortDuration> overload; private SortedMap<LocalDate, EffortDuration> availability; public ResourceLoadChartData(List<DayAssignment> dayAssignments, List<Resource> resources) { this(dayAssignments, resources, null, null); } public ResourceLoadChartData(List<DayAssignment> dayAssignments, List<Resource> resources, LocalDate startInclusive, LocalDate endExclusive) { ContiguousDaysLine<List<DayAssignment>> assignments = ContiguousDaysLine.byDay(dayAssignments); if (startInclusive != null && endExclusive != null) { assignments = assignments.subInterval(startInclusive, endExclusive); } ContiguousDaysLine<EffortDuration> load = assignments.transform(extractLoad()); ContiguousDaysLine<EffortDuration> overload = assignments.transform(extractOverload()); ContiguousDaysLine<EffortDuration> availabilityOnAllResources = assignments.transform(extractAvailabilityOnAllResources(resources)); this.load = toSortedMap(ContiguousDaysLine.min(load, availabilityOnAllResources)); this.overload = toSortedMap(sum(overload, availabilityOnAllResources)); this.availability = toSortedMap(availabilityOnAllResources); } public static IValueTransformer<List<DayAssignment>, EffortDuration> extractOverload() { return compound(effortByResource(), calculateOverload()); } private static IValueTransformer<List<DayAssignment>, Map<Resource, EffortDuration>> effortByResource() { return new IValueTransformer<List<DayAssignment>, Map<Resource, EffortDuration>>() { @Override public Map<Resource, EffortDuration> transform(LocalDate day, List<DayAssignment> previousValue) { Map<Resource, List<DayAssignment>> byResource = DayAssignment.byResource(previousValue); Map<Resource, EffortDuration> result = new HashMap<>(); for (Entry<Resource, List<DayAssignment>> each : byResource.entrySet()) { result.put(each.getKey(), DayAssignment.sum(each.getValue())); } return result; } }; } public static IValueTransformer<Map<Resource, EffortDuration>, EffortDuration> calculateOverload() { return new IValueTransformer<Map<Resource, EffortDuration>, EffortDuration>() { @Override public EffortDuration transform(LocalDate day, Map<Resource, EffortDuration> previousValue) { final PartialDay wholeDay = PartialDay.wholeDay(day); return EffortDuration.sum(previousValue.entrySet(), new IEffortFrom<Entry<Resource, EffortDuration>>() { @Override public EffortDuration from(Entry<Resource, EffortDuration> each) { EffortDuration capacity = calendarCapacityFor(each.getKey(), wholeDay); EffortDuration assigned = each.getValue(); return assigned.minus(min(capacity, assigned)); } }); } }; } public static IValueTransformer<List<DayAssignment>, EffortDuration> extractLoad() { return new IValueTransformer<List<DayAssignment>, EffortDuration>() { @Override public EffortDuration transform(LocalDate day, List<DayAssignment> previousValue) { return DayAssignment.sum(previousValue); } }; } public static IValueTransformer<List<DayAssignment>, EffortDuration> extractAvailabilityOnAssignedResources() { return new IValueTransformer<List<DayAssignment>, EffortDuration>() { @Override public EffortDuration transform(LocalDate day, List<DayAssignment> previousValue) { Set<Resource> resources = getResources(previousValue); return sumCalendarCapacitiesForDay(resources, day); } private Set<Resource> getResources(List<DayAssignment> assignments) { Set<Resource> resources = new HashSet<>(); for (DayAssignment dayAssignment : assignments) { resources.add(dayAssignment.getResource()); } return resources; } }; } private IValueTransformer<List<DayAssignment>, EffortDuration> extractAvailabilityOnAllResources( final List<Resource> resources) { return new IValueTransformer<List<DayAssignment>, EffortDuration>() { @Override public EffortDuration transform(LocalDate day, List<DayAssignment> previousValue) { return sumCalendarCapacitiesForDay(resources, day); } }; } public SortedMap<LocalDate, EffortDuration> getLoad() { return load; } public SortedMap<LocalDate, EffortDuration> getOverload() { return overload; } public SortedMap<LocalDate, EffortDuration> getAvailability() { return availability; } public ILoadChartData on(final LocalDate startInclusive, final LocalDate endExclusive) { final ResourceLoadChartData original = ResourceLoadChartData.this; if (startInclusive == null && endExclusive == null) { return original; } return new ILoadChartData() { @Override public SortedMap<LocalDate, EffortDuration> getOverload() { return filter(original.getOverload()); } @Override public SortedMap<LocalDate, EffortDuration> getLoad() { return filter(original.getLoad()); } @Override public SortedMap<LocalDate, EffortDuration> getAvailability() { return filter(original.getAvailability()); } private SortedMap<LocalDate, EffortDuration> filter(SortedMap<LocalDate, EffortDuration> map) { if (startInclusive != null) { return map.tailMap(startInclusive); } if (endExclusive != null) { return map.headMap(endExclusive); } return map.subMap(startInclusive, endExclusive); } }; } private static EffortDuration sumCalendarCapacitiesForDay(Collection<? extends Resource> resources, LocalDate day) { final PartialDay wholeDay = PartialDay.wholeDay(day); return EffortDuration.sum(resources, new IEffortFrom<Resource>() { @Override public EffortDuration from(Resource each) { return calendarCapacityFor(each, wholeDay); } }); } protected static EffortDuration calendarCapacityFor(Resource resource, PartialDay day) { return resource.getCalendarOrDefault().getCapacityOn(day); } }