/* * 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-2012 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.entities; import static java.util.Collections.emptyList; import static org.libreplan.business.workingday.EffortDuration.min; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import org.apache.commons.lang3.Validate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.joda.time.Days; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.AvailabilityTimeLine; import org.libreplan.business.calendars.entities.ICalendar; import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay; import org.libreplan.business.externalcompanies.entities.ExternalCompany; import org.libreplan.business.orders.entities.AggregatedHoursGroup; import org.libreplan.business.orders.entities.HoursGroup; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.SumChargedEffort; import org.libreplan.business.orders.entities.TaskSource; import org.libreplan.business.planner.entities.AssignedEffortForResource.IAssignedEffortForResource; import org.libreplan.business.planner.entities.AssignedEffortForResource.WithTheLoadOf; import org.libreplan.business.planner.entities.DayAssignment.FilterType; import org.libreplan.business.planner.entities.Dependency.Type; import org.libreplan.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder; import org.libreplan.business.planner.entities.ResourceAllocation.Direction; import org.libreplan.business.planner.entities.allocationalgorithms.AllocationModification; import org.libreplan.business.planner.entities.allocationalgorithms.EffortModification; import org.libreplan.business.planner.entities.allocationalgorithms.ResourcesPerDayModification; import org.libreplan.business.planner.entities.consolidations.Consolidation; import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement; import org.libreplan.business.resources.daos.IResourcesSearcher; import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.scenarios.entities.Scenario; import org.libreplan.business.util.TaskElementVisitor; import org.libreplan.business.util.deepcopy.AfterCopy; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.IntraDayDate; import org.libreplan.business.workingday.IntraDayDate.PartialDay; import org.libreplan.business.workingday.ResourcesPerDay; import org.springframework.transaction.annotation.Transactional; /** * @author Óscar González Fernández <ogonzalez@igalia.com> * @author Manuel Rego Casasnovas <rego@igalia.com> */ public class Task extends TaskElement implements ITaskPositionConstrained { private static final Log LOG = LogFactory.getLog(Task.class); /** * Maximum number of days in order to looking for calendar capacity (defined * to 5 years) */ private final static int MAX_DAYS_LOOKING_CAPACITY = 360 * 5; public static Task createTask(TaskSource taskSource) { Task task = new Task(); OrderElement orderElement = taskSource.getOrderElement(); orderElement.applyInitialPositionConstraintTo(task); Task result = create(task, taskSource); result.initializeDates(); return result; } /** * Needed for import external tasks. * * Creates a Task without initializing the dates. * * @param taskSource * TaskSouce for create the task * * @return Task New Task */ public static Task createTaskWithoutDatesInitialized(TaskSource taskSource) { Task task = new Task(); OrderElement orderElement = taskSource.getOrderElement(); orderElement.applyInitialPositionConstraintTo(task); Task result = create(task, taskSource); return result; } @Override protected void initializeDates() { EffortDuration workHours = EffortDuration.hours(getWorkHours()); DurationBetweenDates duration = fromFixedDuration(workHours); IntraDayDate start = getIntraDayStartDate(); if ( start != null ) { setIntraDayEndDate(duration.fromStartToEnd(start)); } else { IntraDayDate end = getIntraDayEndDate(); setIntraDayStartDate(duration.fromEndToStart(end)); } } /** * Calculates end date for a task, starting from start until fulfilling * number of hours. * * For tasks with limiting resources it's needed to resize a task if the * number of hours allocated to a resource changes. In non limiting * resources, the task is resized because when the number of hours changes, * new days assignments are generated, and then the task is resized * accordingly. * * @param hours */ public void resizeToHours(int hours) { Validate.isTrue(isLimiting()); EffortDuration workHours = EffortDuration.hours(hours); DurationBetweenDates duration = new DurationBetweenDates(0, workHours); setIntraDayEndDate(duration.fromStartToEnd(getIntraDayStartDate())); } private CalculatedValue calculatedValue = CalculatedValue.END_DATE; private TaskStatusEnum currentStatus = null; private Set<ResourceAllocation<?>> resourceAllocations = new HashSet<>(); @Valid private Set<ResourceAllocation<?>> getResourceAllocations() { return new HashSet<>(resourceAllocations); } @SuppressWarnings("unused") @AfterCopy private void ifLimitingAllocationRemove() { if ( isLimiting() ) { resourceAllocations.clear(); } } private TaskPositionConstraint positionConstraint = new TaskPositionConstraint(); private SubcontractedTaskData subcontractedTaskData; private Integer priority; private Consolidation consolidation; private Integer workableDays; private Direction lastAllocationDirection = Direction.FORWARD; /** * Constructor for hibernate. Do not use! */ public Task() { } @SuppressWarnings("unused") @AssertTrue(message = "element associated to a task must be not empty") private boolean isTheOrderElementMustBeNotNullConstraint() { return getOrderElement() != null; } public HoursGroup getHoursGroup() { return getTaskSource().getHoursGroups().iterator().next(); } public Set<Criterion> getCriterions() { return Collections.unmodifiableSet(getHoursGroup().getValidCriterions()); } public Integer getHoursSpecifiedAtOrder() { return getWorkHours(); } public int getAssignedHours() { return AggregateOfResourceAllocations.createFromSatisfied(resourceAllocations).getTotalHours(); } public EffortDuration getAssignedEffort() { return AggregateOfResourceAllocations.createFromSatisfied(resourceAllocations).getTotalEffort(); } private EffortDuration getTotalNonConsolidatedEffort() { return AggregateOfResourceAllocations.createFromAll(resourceAllocations).getNonConsolidatedEffort(); } public int getTotalHours() { return (getTaskSource() != null) ? getTaskSource().getTotalHours() : 0; } @Override public boolean isLeaf() { return true; } @Override public List<TaskElement> getChildren() { return Collections.emptyList(); } public Set<ResourceAllocation<?>> getSatisfiedResourceAllocations() { Set<ResourceAllocation<?>> result = new HashSet<>(); if ( isLimiting() ) { result.addAll(getLimitingResourceAllocations()); } else { result.addAll(ResourceAllocation.getSatisfied(resourceAllocations)); } return Collections.unmodifiableSet(result); } @Override @Transactional(readOnly = true) public Set<ResourceAllocation<?>> getAllResourceAllocations() { return Collections.unmodifiableSet(resourceAllocations); } public Set<ResourceAllocation<?>> getLimitingResourceAllocations() { Set<ResourceAllocation<?>> result = new HashSet<>(); for (ResourceAllocation<?> each: resourceAllocations) { if ( each.isLimiting() ) { result.add(each); } } return Collections.unmodifiableSet(result); } public Set<ResourceAllocation<?>> getNonLimitingResourceAllocations() { Set<ResourceAllocation<?>> result = new HashSet<>(); for (ResourceAllocation<?> each: resourceAllocations) { if ( !each.isLimiting() ) { result.add(each); } } return Collections.unmodifiableSet(result); } public boolean isLimiting() { return !(getLimitingResourceAllocations().isEmpty()); } private ResourceAllocation<?> getAssociatedLimitingResourceAllocation() { Set<ResourceAllocation<?>> resourceAllocations = getLimitingResourceAllocations(); return (resourceAllocations.size() > 0) ? resourceAllocations.iterator().next() : null; } public LimitingResourceQueueElement getAssociatedLimitingResourceQueueElementIfAny() { if ( !isLimiting() ) { throw new IllegalStateException("this is not a limiting task"); } return getAssociatedLimitingResourceAllocation().getLimitingResourceQueueElement(); } public boolean isLimitingAndHasDayAssignments() { ResourceAllocation<?> resourceAllocation = getAssociatedLimitingResourceAllocation(); return resourceAllocation != null && resourceAllocation.isLimitingAndHasDayAssignments(); } public void addResourceAllocation(ResourceAllocation<?> resourceAllocation) { addResourceAllocation(resourceAllocation, true); } public void addResourceAllocation(ResourceAllocation<?> resourceAllocation, boolean generateDayAssignments) { if ( !resourceAllocation.getTask().equals(this) ) { throw new IllegalArgumentException("the resourceAllocation's task must be this task"); } resourceAllocations.add(resourceAllocation); if ( generateDayAssignments ) { resourceAllocation.associateAssignmentsToResource(); } } public ResourceAllocation<?> getResourceAllocation() { Validate.isTrue(isLimiting()); return resourceAllocations.isEmpty() ? null : resourceAllocations.iterator().next(); } public void setResourceAllocation(ResourceAllocation<?> resourceAllocation) { Validate.isTrue(resourceAllocation.isLimiting()); removeAllResourceAllocations(); resourceAllocations.add(resourceAllocation); } public void removeResourceAllocation(ResourceAllocation<?> resourceAllocation) { resourceAllocation.detach(); resourceAllocations.remove(resourceAllocation); } public CalculatedValue getCalculatedValue() { if ( calculatedValue == null ) { return CalculatedValue.END_DATE; } return calculatedValue; } public void setCalculatedValue(CalculatedValue calculatedValue) { Validate.notNull(calculatedValue); this.calculatedValue = calculatedValue; } /** * Checks if there isn't any {@link Worker} repeated in the {@link Set} of * {@link ResourceAllocation} of this {@link Task}. * @return <code>true</code> if the {@link Task} is valid, that means there * isn't any {@link Worker} repeated. */ public boolean isValidResourceAllocationWorkers() { Set<Long> workers = new HashSet<>(); for (ResourceAllocation<?> resourceAllocation : resourceAllocations) { if ( resourceAllocation instanceof SpecificResourceAllocation ) { Resource resource = ((SpecificResourceAllocation) resourceAllocation).getResource(); if ( resource != null ) { if ( workers.contains(resource.getId()) ) { return false; } else { workers.add(resource.getId()); } } } } return true; } public Set<GenericResourceAllocation> getGenericResourceAllocations() { return new HashSet<>(ResourceAllocation.getOfType( GenericResourceAllocation.class, getSatisfiedResourceAllocations())); } public Set<SpecificResourceAllocation> getSpecificResourceAllocations() { return new HashSet<>(ResourceAllocation.getOfType( SpecificResourceAllocation.class, getSatisfiedResourceAllocations())); } public static class ModifiedAllocation { public static List<ModifiedAllocation> copy( Scenario onScenario, Collection<ResourceAllocation<?>> resourceAllocations) { List<ModifiedAllocation> result = new ArrayList<>(); for (ResourceAllocation<?> resourceAllocation : resourceAllocations) { result.add(new ModifiedAllocation(resourceAllocation, resourceAllocation.copy(onScenario))); } return result; } public static List<ResourceAllocation<?>> modified(Collection<? extends ModifiedAllocation> collection) { List<ResourceAllocation<?>> result = new ArrayList<>(); for (ModifiedAllocation modifiedAllocation : collection) { result.add(modifiedAllocation.getModification()); } return result; } public static List<ResourceAllocation<?>> originals (Collection<? extends ModifiedAllocation> modifiedAllocations) { List<ResourceAllocation<?>> result = new ArrayList<>(); for (ModifiedAllocation each : modifiedAllocations) { result.add(each.getOriginal()); } return result; } private final ResourceAllocation<?> original; private final ResourceAllocation<?> modification; public ModifiedAllocation(ResourceAllocation<?> original, ResourceAllocation<?> modification) { Validate.notNull(original); Validate.notNull(modification); this.original = original; this.modification = modification; } public ResourceAllocation<?> getOriginal() { return original; } public ResourceAllocation<?> getModification() { return modification; } } public void mergeAllocation( Scenario scenario, final IntraDayDate start, final IntraDayDate end, Integer newWorkableDays, CalculatedValue calculatedValue, List<ResourceAllocation<?>> newAllocations, List<ModifiedAllocation> modifications, Collection<? extends ResourceAllocation<?>> toRemove) { this.calculatedValue = calculatedValue; this.workableDays = calculatedValue == CalculatedValue.END_DATE ? null : newWorkableDays; setIntraDayStartDate(start); setIntraDayEndDate(end); for (ModifiedAllocation pair : modifications) { Validate.isTrue(resourceAllocations.contains(pair.getOriginal())); pair.getOriginal().mergeAssignmentsAndResourcesPerDay(scenario, pair.getModification()); } remove(toRemove); addAllocations(scenario, newAllocations); } private void remove(Collection<? extends ResourceAllocation<?>> toRemove) { for (ResourceAllocation<?> resourceAllocation : toRemove) { removeResourceAllocation(resourceAllocation); } } private void addAllocations(Scenario scenario, List<ResourceAllocation<?>> newAllocations) { for (ResourceAllocation<?> resourceAllocation : newAllocations) { resourceAllocation.switchToScenario(scenario); addResourceAllocation(resourceAllocation); } } public void explicityMoved(IntraDayDate startDate, IntraDayDate endDate) { getPositionConstraint().explicityMovedTo(startDate, endDate, getOrderElement().getOrder().getSchedulingMode()); } public TaskPositionConstraint getPositionConstraint() { if ( positionConstraint == null ) { positionConstraint = new TaskPositionConstraint(); } return positionConstraint; } private static class ModificationsResult<T extends AllocationModification> { static <T extends AllocationModification> ModificationsResult<T> create( List<ResourceAllocation<?>> original, List<T> canBeModified) { List<ResourceAllocation<?>> beingModified = AllocationModification.getBeingModified(canBeModified); List<ResourceAllocation<?>> noLongerValid = new ArrayList<>(); for (ResourceAllocation<?> each : original) { if ( !beingModified.contains(each) ) { noLongerValid.add(each); } } return new ModificationsResult<>(canBeModified, noLongerValid); } private final List<T> valid; private final List<ResourceAllocation<?>> noLongerValid; private ModificationsResult(List<T> valid, List<ResourceAllocation<?>> noLongerValid) { this.valid = Collections.unmodifiableList(valid); this.noLongerValid = Collections.unmodifiableList(noLongerValid); } public List<T> getBeingModified() { return valid; } public List<ResourceAllocation<?>> getNoLongerValid() { return noLongerValid; } } private static class WithPotentiallyNewResources { protected final IResourcesSearcher searcher; public WithPotentiallyNewResources(IResourcesSearcher searcher) { Validate.notNull(searcher); this.searcher = searcher; } public ModificationsResult<EffortModification> getHoursModified(List<ResourceAllocation<?>> allocations) { List<EffortModification> canBeModified = EffortModification.withNewResources(allocations, searcher); return ModificationsResult.create(allocations, canBeModified); } public ModificationsResult<ResourcesPerDayModification> getResourcesPerDayModified( List<ResourceAllocation<?>> allocations) { List<ResourcesPerDayModification> canBeModified = ResourcesPerDayModification.withNewResources(allocations, searcher); return ModificationsResult.create(allocations, canBeModified); } } public void copyAssignmentsFromOneScenarioToAnother(Scenario from, Scenario to) { for (ResourceAllocation<?> each : getAllResourceAllocations()) { each.copyAssignmentsFromOneScenarioToAnother(from, to); } } @Override protected IDatesHandler createDatesHandler(final Scenario scenario, final IResourcesSearcher searcher) { return new IDatesHandler() { @Override public void moveTo(IntraDayDate newStartDate) { IntraDayDate previousStart = getIntraDayStartDate(); if ( previousStart.equals(newStartDate) ) { return; } setIntraDayEndDate(calculateEndKeepingLength(newStartDate)); setIntraDayStartDate(newStartDate); doReassignment(Direction.FORWARD); } private void doReassignment(Direction direction) { reassign(scenario, direction, new WithPotentiallyNewResources(searcher)); } @Override public void moveEndTo(IntraDayDate newEnd) { if ( getIntraDayEndDate().equals(newEnd) ) { return; } setIntraDayStartDate(calculateNewStartGivenEnd(newEnd)); setIntraDayEndDate(newEnd); doReassignment(Direction.BACKWARD); } private IntraDayDate calculateNewStartGivenEnd(IntraDayDate newEnd) { return calculateStartKeepingLength(newEnd); } @Override public void resizeTo(IntraDayDate endDate) { if ( !canBeResized() || getIntraDayEndDate().equals(endDate) ) { return; } setIntraDayEndDate(endDate); updateWorkableDays(); doReassignment(getAllocationDirection()); } private void updateWorkableDays() { assert calculatedValue != CalculatedValue.END_DATE; workableDays = getWorkableDaysBetweenDates(); } }; } public IntraDayDate calculateEndKeepingLength(IntraDayDate newStartDate) { DurationBetweenDates durationBetweenDates = getDurationBetweenDates(); return durationBetweenDates.fromStartToEnd(newStartDate); } public IntraDayDate calculateEndGivenWorkableDays(int days) { Validate.isTrue(days >= 0); DurationBetweenDates duration = fromFixedDuration(days); return duration.fromStartToEnd(getIntraDayStartDate()); } public IntraDayDate calculateStartGivenWorkableDays(int days) { Validate.isTrue(days >= 0); DurationBetweenDates duration = fromFixedDuration(days); return duration.fromEndToStart(getIntraDayEndDate()); } private IntraDayDate calculateStartKeepingLength(IntraDayDate newEnd) { DurationBetweenDates durationBetweenDates = getDurationBetweenDates(); return durationBetweenDates.fromEndToStart(newEnd); } private DurationBetweenDates getDurationBetweenDates() { if ( workableDays != null ) { return fromFixedDuration(workableDays); } else { return fromCurrentDuration(); } } private DurationBetweenDates fromFixedDuration(int fixedNumberOfWorkableDays) { return new DurationBetweenDates(fixedNumberOfWorkableDays, EffortDuration.zero()); } private DurationBetweenDates fromCurrentDuration() { IntraDayDate start = getIntraDayStartDate(); IntraDayDate end = getIntraDayEndDate(); int calculatedWorkableDays = getWorkableDaysFrom(start.roundUp(), end.roundDown()); EffortDuration extraDuration = getExtraDurationAtStart(start).plus(end.getEffortDuration()); return new DurationBetweenDates(calculatedWorkableDays, extraDuration); } private EffortDuration getExtraDurationAtStart(IntraDayDate start) { if ( start.getEffortDuration().isZero() ) { return EffortDuration.zero(); } ICalendar calendar = getNullSafeCalendar(); EffortDuration capacity = calendar.getCapacityOn(PartialDay.wholeDay(start.getDate())); return capacity.minus(min(start.getEffortDuration(), capacity)); } private ICalendar getNullSafeCalendar() { return getCalendar() != null ? getCalendar() : SameWorkHoursEveryDay.getDefaultWorkingDay(); } private DurationBetweenDates fromFixedDuration(EffortDuration duration) { return new DurationBetweenDates(0, duration); } private class DurationBetweenDates { private final int numberOfWorkableDays; private final EffortDuration remainderDuration; private final ICalendar calendar; private DurationBetweenDates(int numberOfWorkableDays, EffortDuration remainderDuration) { this.numberOfWorkableDays = numberOfWorkableDays; this.remainderDuration = remainderDuration; this.calendar = getNullSafeCalendar(); } public IntraDayDate fromStartToEnd(IntraDayDate newStartDate) { LocalDate resultDay = afterSomeWorkableDays(newStartDate.getDate(), numberOfWorkableDays); return plusDuration( IntraDayDate.startOfDay(resultDay), remainderDuration.plus(newStartDate.getEffortDuration())); } private LocalDate afterSomeWorkableDays(LocalDate start, int workableDays) { LocalDate result = start; for (int i = 0; i < workableDays; result = result.plusDays(1)) { if ( isWorkable(result) ) { i++; } } return result; } private IntraDayDate plusDuration(IntraDayDate start, EffortDuration remaining) { IntraDayDate result = IntraDayDate.startOfDay(start.getDate()); remaining = remaining.plus(start.getEffortDuration()); EffortDuration originalRemaining = remaining; LocalDate startDate = start.getDate(); LocalDate current = startDate; if ( !canBeFulfilled(start, originalRemaining) ) { return roughApproximationDueToNotFullfilingCalendar(startDate, originalRemaining); } while (!remaining.isZero()) { EffortDuration capacity = calendar.getCapacityOn(PartialDay.wholeDay(current)); result = IntraDayDate.create(current, remaining); remaining = remaining.minus(min(capacity, remaining)); current = current.plusDays(1); if ( Days.daysBetween(startDate, current).getDays() > MAX_DAYS_LOOKING_CAPACITY ) { LOG.error("thereAreCapacityFor didn't detect that it didn't" + " really have enough capacity to fulfill the required hours" + " or this capacity is more than " + MAX_DAYS_LOOKING_CAPACITY + " in the future"); return roughApproximationDueToNotFullfilingCalendar(startDate, originalRemaining); } } return result; } private boolean canBeFulfilled(IntraDayDate start, EffortDuration originalRemaining) { AvailabilityTimeLine availability = AvailabilityTimeLine.allValid(); availability.invalidUntil(start.getDate()); return calendar.thereAreCapacityFor(availability, ResourcesPerDay.amount(1), originalRemaining); } private IntraDayDate roughApproximationDueToNotFullfilingCalendar( LocalDate startDate, EffortDuration originalRemaining) { LOG.warn("Calendar " + calendar + " doesn't have enough capacity, " + "using 8h per day to calculate end date for the task"); return IntraDayDate.create( startDate.plusDays(originalRemaining.getHours() / 8), EffortDuration.zero()); } public IntraDayDate fromEndToStart(IntraDayDate newEnd) { LocalDate resultDay = someWorkableDaysBefore(newEnd.getDate(), numberOfWorkableDays); return minusDuration( plusDuration(IntraDayDate.startOfDay(resultDay), newEnd.getEffortDuration()), remainderDuration); } private LocalDate someWorkableDaysBefore(LocalDate end, int workableDays) { LocalDate result = end; for (int i = 0; i < workableDays; result = result.minusDays(1)) { if ( isWorkable(result.minusDays(1)) ) { i++; } } return result; } private IntraDayDate minusDuration(IntraDayDate date, EffortDuration decrement) { IntraDayDate result = IntraDayDate.create( date.getDate(), date.getEffortDuration().minus(min(decrement, date.getEffortDuration()))); decrement = decrement.minus(min(date.getEffortDuration(), decrement)); LocalDate resultDay = date.getDate(); while (!decrement.isZero()) { resultDay = resultDay.minusDays(1); EffortDuration capacity = calendar.getCapacityOn(PartialDay.wholeDay(resultDay)); result = IntraDayDate.create(resultDay, capacity.minus(min(capacity, decrement))); decrement = decrement.minus(min(capacity, decrement)); } return result; } } /** * The allocation direction in which the allocation must be done */ public Direction getAllocationDirection() { if ( lastAllocationDirection == null || hasConsolidations() ) { return Direction.FORWARD; } return lastAllocationDirection; } public void reassignAllocationsWithNewResources(Scenario scenario, IResourcesSearcher searcher) { reassign(scenario, getAllocationDirection(), new WithPotentiallyNewResources(searcher)); } private void reassign(Scenario onScenario, Direction direction, WithPotentiallyNewResources strategy) { try { this.lastAllocationDirection = direction; if ( isLimiting() ) { return; } List<ModifiedAllocation> copied = ModifiedAllocation.copy(onScenario, getResourceAllocations()); List<ResourceAllocation<?>> toBeModified = ModifiedAllocation.modified(copied); if ( toBeModified.isEmpty() ) { return; } setCustomAssignedEffortForResource(copied); doAllocation(strategy, direction, toBeModified); updateDerived(copied); List<ResourceAllocation<?>> newAllocations = emptyList(), removedAllocations = emptyList(); mergeAllocation( onScenario, getIntraDayStartDate(), getIntraDayEndDate(), workableDays, calculatedValue, newAllocations, copied, removedAllocations); } catch (Exception e) { LOG.error("reassignment for task: " + this + " couldn't be completed", e); } } private void setCustomAssignedEffortForResource(List<ModifiedAllocation> modifiedAllocations) { List<ResourceAllocation<?>> originals = ModifiedAllocation.originals(modifiedAllocations); IAssignedEffortForResource discounting = AssignedEffortForResource.effortDiscounting(originals); List<ResourceAllocation<?>> beingModified = ModifiedAllocation.modified(modifiedAllocations); WithTheLoadOf allNewLoad = AssignedEffortForResource.withTheLoadOf(beingModified); List<GenericResourceAllocation> generic = ResourceAllocation.getOfType(GenericResourceAllocation.class, beingModified); for (GenericResourceAllocation each : generic) { each.setAssignedEffortForResource( AssignedEffortForResource.sum(allNewLoad.withoutConsidering(each), discounting)); } } private void doAllocation(WithPotentiallyNewResources strategy, Direction direction, List<ResourceAllocation<?>> toBeModified) { ModificationsResult<ResourcesPerDayModification> modificationsResult = strategy.getResourcesPerDayModified(toBeModified); markAsUnsatisfied(modificationsResult.getNoLongerValid()); List<ResourcesPerDayModification> allocations = modificationsResult.getBeingModified(); if ( allocations.isEmpty() ) { LOG.warn("all allocations for task " + this + " have no valid data that could be used"); return; } switch (calculatedValue) { case NUMBER_OF_HOURS: ResourceAllocation.allocating(allocations).allocateOnTaskLength(); break; case END_DATE: IntraDayDate date = ResourceAllocation.allocating(allocations) .untilAllocating(direction, getTotalNonConsolidatedEffort()); if ( direction == Direction.FORWARD ) { setIntraDayEndDate(date); } else { setIntraDayStartDate(date); } break; case RESOURCES_PER_DAY: ModificationsResult<EffortModification> hoursModificationResult = strategy.getHoursModified(toBeModified); markAsUnsatisfied(hoursModificationResult.getNoLongerValid()); List<EffortModification> hoursModified = hoursModificationResult.getBeingModified(); if ( hoursModified.isEmpty() ) { LOG.warn("all allocations for task " + this + " can't be used"); return; } ResourceAllocation.allocatingHours(hoursModified).allocateUntil(getIntraDayEndDate()); break; default: throw new RuntimeException("cant handle: " + calculatedValue); } AssignmentFunction.applyAssignmentFunctionsIfAny(toBeModified); } private void markAsUnsatisfied(Collection<? extends ResourceAllocation<?>> noLongerValid) { for (ResourceAllocation<?> each : noLongerValid) { each.markAsUnsatisfied(); } } private void updateDerived(List<ModifiedAllocation> allocations) { for (ModifiedAllocation each : allocations) { ResourceAllocation<?> original = each.getOriginal(); if ( !original.getDerivedAllocations().isEmpty() ) { IWorkerFinder workersFinder = createFromExistentDerivedAllocationsFinder(original); each.getModification().createDerived(workersFinder); } } } private IWorkerFinder createFromExistentDerivedAllocationsFinder(ResourceAllocation<?> original) { Set<DerivedAllocation> derivedAllocations = original.getDerivedAllocations(); final Set<Worker> allWorkers = new HashSet<>(); for (DerivedAllocation each : derivedAllocations) { allWorkers.addAll(Resource.workers(each.getResources())); } return new IWorkerFinder() { @Override public Collection<Worker> findWorkersMatching(Collection<? extends Criterion> requiredCriterions) { if ( requiredCriterions.isEmpty() ) { return new ArrayList<>(); } Collection<Worker> result = new ArrayList<>(); for (Worker each : allWorkers) { if ( each.satisfiesCriterions(requiredCriterions) ) { result.add(each); } } return result; } }; } public List<AggregatedHoursGroup> getAggregatedByCriterions() { return getTaskSource().getAggregatedByCriterions(); } public void setSubcontractedTaskData(SubcontractedTaskData subcontractedTaskData) { this.subcontractedTaskData = subcontractedTaskData; } @Valid public SubcontractedTaskData getSubcontractedTaskData() { return subcontractedTaskData; } public ExternalCompany getSubcontractedCompany() { return subcontractedTaskData.getExternalCompany(); } public void removeAllSatisfiedResourceAllocations() { Set<ResourceAllocation<?>> resourceAllocations = getSatisfiedResourceAllocations(); for (ResourceAllocation<?> resourceAllocation : resourceAllocations) { removeResourceAllocation(resourceAllocation); } } public void removeAllResourceAllocations() { for (Iterator<ResourceAllocation<?>> i = resourceAllocations.iterator(); i.hasNext();) { ResourceAllocation<?> each = i.next(); removeResourceAllocation(each); } } public boolean isSubcontracted() { return (subcontractedTaskData != null); } public String getSubcontractionName() { return subcontractedTaskData.getExternalCompany().getName(); } public boolean isSubcontractedAndWasAlreadySent() { return (subcontractedTaskData != null) && (!subcontractedTaskData.getState().equals(SubcontractState.PENDING_INITIAL_SEND)); } public boolean hasSomeSatisfiedAllocation() { return !getSatisfiedResourceAllocations().isEmpty(); } @Override protected boolean canBeResized() { return calculatedValue != CalculatedValue.END_DATE || resourceAllocations.isEmpty(); } @Override public boolean canBeExplicitlyResized() { return canBeResized() && !isSubcontracted() && !isManualAnyAllocation(); } @Override public boolean isMilestone() { return false; } public void removeSubcontractCommunicationDate() { if ( subcontractedTaskData != null ) { subcontractedTaskData.setSubcontractCommunicationDate(null); } } public boolean hasResourceAllocations() { return !resourceAllocations.isEmpty(); } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } public void setConsolidation(Consolidation consolidation) { this.consolidation = consolidation; } @Valid public Consolidation getConsolidation() { return consolidation; } @Override public boolean hasLimitedResourceAllocation() { return !getLimitingResourceAllocations().isEmpty(); } public boolean hasConsolidations() { return ((consolidation != null) && (!consolidation.isEmpty())); } public IntraDayDate getFirstDayNotConsolidated() { if ( consolidation != null ) { LocalDate until = consolidation.getConsolidatedUntil(); if ( until != null ) { return IntraDayDate.startOfDay(until.plusDays(1)); } } return getIntraDayStartDate(); } public void updateAssignmentsConsolidatedValues() { for (ResourceAllocation<?> each : getAllResourceAllocations()) { each.updateAssignmentsConsolidatedValues(); } } public Integer getWorkableDays() { return (workableDays == null) ? getWorkableDaysBetweenDates() : workableDays; } public Integer getDaysBetweenDates() { Days daysBetween = Days.daysBetween(getStartAsLocalDate(), getIntraDayEndDate().asExclusiveEnd()); return daysBetween.getDays(); } public Integer getSpecifiedWorkableDays() { return workableDays; } private Integer getWorkableDaysBetweenDates() { LocalDate end = getIntraDayEndDate().asExclusiveEnd(); return getWorkableDaysUntil(end); } public Integer getWorkableDaysUntil(LocalDate end) { return getWorkableDaysFrom(getStartAsLocalDate(), end); } public Integer getWorkableDaysFrom(LocalDate startInclusive, LocalDate endExclusive) { int result = 0; for (LocalDate current = startInclusive; current.compareTo(endExclusive) < 0; current = current.plusDays(1)) { if ( isWorkable(current) ) { result++; } } return result; } /* Older methods didn't consider until dates more recent than * task end date */ public Integer getWorkableDaysFromLimitedByEndOfTheTask(LocalDate end) { return getWorkableDaysFromLimitedByEndOfTheTask(getStartAsLocalDate(), end); } public Integer getWorkableDaysFromLimitedByEndOfTheTask(LocalDate startInclusive, LocalDate endExclusive) { int result = 0; if( endExclusive.compareTo(this.getEndAsLocalDate()) > 0 ) { endExclusive = getIntraDayEndDate().asExclusiveEnd(); } for (LocalDate current = startInclusive; current.compareTo(endExclusive) < 0; current = current.plusDays(1)) { if ( isWorkable(current) ) { result++; } } return result; } private boolean isWorkable(LocalDate day) { ICalendar calendar = getCalendar(); assert calendar != null; return !calendar.getCapacityOn(PartialDay.wholeDay(day)).isZero(); } public static void convertOnStartInFixedDate(Task task) { TaskPositionConstraint taskConstraint = task.getPositionConstraint(); if ( taskConstraint.isValid(PositionConstraintType.START_IN_FIXED_DATE, task.getIntraDayStartDate()) ) { taskConstraint.update(PositionConstraintType.START_IN_FIXED_DATE, task.getIntraDayStartDate()); } } public boolean isManualAnyAllocation() { for (ResourceAllocation<?> each : resourceAllocations) { if ( each.isManualAssignmentFunction() ) { return true; } } return false; } @Override public boolean isTask() { return true; } @Override public EffortDuration getTheoreticalCompletedTimeUntilDate(Date date) { return AggregateOfDayAssignments.createByDataRange( this.getDayAssignments(FilterType.KEEP_ALL), this.getStartDate(), date) .getTotalTime(); } public TaskStatusEnum getTaskStatus() { if ( this.isFinished() ) { return TaskStatusEnum.FINISHED; } else if ( this.isInProgress() ) { return TaskStatusEnum.IN_PROGRESS; } else if ( this.isReadyToStart() ) { return TaskStatusEnum.READY_TO_START; } else if ( this.isBlocked() ){ return TaskStatusEnum.BLOCKED; } else { throw new RuntimeException("Unknown task status. You've found a bug :)"); } } /* If the status of the task was needed in the past was because * a TaskGroup needed to calculate children status, but only asked * if this task was FINISHED or IN_PROGRESS. Thus, there is no need * to cache other statutes because they only will be queried once. */ @Override public boolean isFinished() { if ( this.currentStatus != null ) { return this.currentStatus == TaskStatusEnum.FINISHED; } else { boolean outcome = this.advancePercentageIsOne(); if ( outcome == true ) { this.currentStatus = TaskStatusEnum.FINISHED; } return outcome; } } @Override public boolean isInProgress() { if ( this.currentStatus != null ) { return this.currentStatus == TaskStatusEnum.IN_PROGRESS; } else { boolean advanceBetweenZeroAndOne = this.advancePercentageIsGreaterThanZero() && !advancePercentageIsOne(); boolean outcome = advanceBetweenZeroAndOne || this.hasAttachedWorkReports(); if ( outcome == true ) { this.currentStatus = TaskStatusEnum.IN_PROGRESS; } return outcome; } } public boolean isReadyToStart() { if ( !this.advancePercentageIsZero() || this.hasAttachedWorkReports() ) { return false; } Set<Dependency> dependencies = getDependenciesWithThisDestinationAndAllParents(); for (Dependency dependency: dependencies) { Type dependencyType = dependency.getType(); if ( dependencyType.equals(Type.END_START) ) { if ( !dependency.getOrigin().isFinished() ) { return false; } } else if ( dependencyType.equals(Type.START_START) ) { if ( !dependency.getOrigin().isFinished() && !dependency.getOrigin().isInProgress() ) { return false; } } } return true; } public boolean isBlocked() { if ( !this.advancePercentageIsZero() || this.hasAttachedWorkReports() ) { return false; } Set<Dependency> dependencies = getDependenciesWithThisDestinationAndAllParents(); for (Dependency dependency: dependencies) { Type dependencyType = dependency.getType(); if ( dependencyType.equals(Type.END_START) ) { if ( !dependency.getOrigin().isFinished() ) { return true; } } else if ( dependencyType.equals(Type.START_START) ) { if ( !dependency.getOrigin().isFinished() && !dependency.getOrigin().isInProgress() ) { return true; } } } return false; } private boolean advancePercentageIsGreaterThanZero() { return this.getAdvancePercentage().compareTo(BigDecimal.ZERO) > 0; } private boolean advancePercentageIsZero() { return this.getAdvancePercentage().compareTo(BigDecimal.ZERO) == 0; } private boolean advancePercentageIsOne() { return this.getAdvancePercentage().compareTo(BigDecimal.ONE) == 0; } private boolean hasAttachedWorkReports() { SumChargedEffort sumChargedEffort = this.getOrderElement().getSumChargedEffort(); return sumChargedEffort != null && !sumChargedEffort.isZero(); } @Override public void acceptVisitor(TaskElementVisitor visitor) { visitor.visit(this); } @Override public void resetStatus() { this.currentStatus = null; } @Override public boolean isAnyTaskWithConstraint(PositionConstraintType type) { return getPositionConstraint().getConstraintType().equals(type); } @Override public void setParent(TaskGroup taskGroup) { super.setParent(taskGroup); } @Override public void setTaskSource(TaskSource taskSource) { super.setTaskSource(taskSource); } }