private static TimeSpan GetWork(ProjectData project, TaskId taskId)
        {
            var hasAssignments = project.GetAssignments(taskId).Any();

            if (!hasAssignments)
            {
                return(project.Get(TaskFields.Duration, taskId));
            }

            return(project.Get(TaskFields.Work, taskId));
        }
        private static void ComputeEarlyStartAndFinish(ProjectData project, TaskId taskId, TaskLink predecessorLink, out DateTimeOffset earlyStart, out DateTimeOffset earlyFinish)
        {
            var calendar = project.Information.Calendar;
            var type     = predecessorLink.Type;
            var predecessorEarlyStart  = project.Get(TaskFields.EarlyStart, predecessorLink.PredecessorId);
            var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, predecessorLink.PredecessorId);

            switch (type)
            {
            case TaskLinkType.FinishToStart:
                earlyStart = predecessorEarlyFinish;
                break;

            case TaskLinkType.StartToStart:
                earlyStart = predecessorEarlyStart;
                break;

            case TaskLinkType.FinishToFinish:
                earlyFinish = predecessorEarlyFinish;
                ComputeStart(project, taskId, out earlyStart, ref earlyFinish);
                break;

            case TaskLinkType.StartToFinish:
                earlyFinish = predecessorEarlyStart;
                ComputeStart(project, taskId, out earlyStart, ref earlyFinish);
                break;

            default:
                throw new Exception($"Unexpected case label {type}");
            }

            if (predecessorLink.Lag != TimeSpan.Zero)
            {
                earlyStart = project.Information.Calendar.AddWork(earlyStart, predecessorLink.Lag);
            }

            var mustSnap = type == TaskLinkType.FinishToFinish;

            if (mustSnap && earlyStart < project.Information.Start)
            {
                earlyStart = project.Information.Start;
            }

            ComputeFinish(project, taskId, ref earlyStart, out earlyFinish);

            // In case of Start-to-Finish, the end date is snapped to a start time
            // unless there is positive lag.

            if (type == TaskLinkType.StartToFinish && predecessorLink.Lag <= TimeSpan.Zero)
            {
                earlyFinish = calendar.FindWorkStart(earlyFinish);
            }
        }
Exemplo n.º 3
0
        private static ProjectData SetAssignmentWork(ProjectData project, AssignmentId id, TimeSpan value)
        {
            var taskId            = project.Get(AssignmentFields.TaskId, id);
            var oldTaskWork       = project.Get(TaskFields.Work, taskId);
            var oldAssignmentWork = project.Get(AssignmentFields.Work, id);

            var newAssignmentWork = value;
            var newTaskWork       = oldTaskWork + newAssignmentWork - oldAssignmentWork;

            return(project.SetRaw(TaskFields.Work, taskId, newTaskWork)
                   .SetRaw(AssignmentFields.Work, id, newAssignmentWork));
        }
        private static ProjectData Finalize(this ProjectData project)
        {
            var calendar = project.Information.Calendar;

            foreach (var taskId in project.Tasks)
            {
                var earlyStart  = project.Get(TaskFields.EarlyStart, taskId);
                var earlyFinish = project.Get(TaskFields.EarlyFinish, taskId);
                var lateStart   = project.Get(TaskFields.LateStart, taskId);
                var lateFinish  = project.Get(TaskFields.LateFinish, taskId);

                // Set start, finish, and duration

                var start    = earlyStart;
                var finish   = earlyFinish;
                var duration = calendar.GetWork(start, finish);

                project = project.SetRaw(TaskFields.Start, taskId, start)
                          .SetRaw(TaskFields.Finish, taskId, finish)
                          .SetRaw(TaskFields.Duration, taskId, duration);

                // Set start slack, finish slack, total slack, and criticality

                var startSlack  = calendar.GetWork(earlyStart, lateStart);
                var finishSlack = calendar.GetWork(earlyFinish, lateFinish);
                var totalSlack  = startSlack <= finishSlack ? startSlack : finishSlack;
                var isCritical  = totalSlack == TimeSpan.Zero;

                project = project.SetRaw(TaskFields.StartSlack, taskId, startSlack)
                          .SetRaw(TaskFields.FinishSlack, taskId, finishSlack)
                          .SetRaw(TaskFields.TotalSlack, taskId, totalSlack)
                          .SetRaw(TaskFields.IsCritical, taskId, isCritical);

                // Set free slack

                var freeSlack = GetFreeSlack(project, taskId);
                project = project.SetRaw(TaskFields.FreeSlack, taskId, freeSlack);
            }

            // Set assignment start and finish

            foreach (var assignmentId in project.Assignments)
            {
                var taskId    = project.Get(AssignmentFields.TaskId, assignmentId);
                var taskStart = project.Get(TaskFields.Start, taskId);

                var assignmentWork  = project.Get(AssignmentFields.Work, assignmentId);
                var assignmentUnits = project.Get(AssignmentFields.Units, assignmentId);
                var assignmentStart = taskStart;

                var duration = TimeSpan.FromHours(assignmentWork.TotalHours / assignmentUnits);
                ComputeFinish(project.Information.Calendar, ref assignmentStart, out var assignmentFinish, duration);

                project = project.SetRaw(AssignmentFields.Start, assignmentId, assignmentStart)
                          .SetRaw(AssignmentFields.Finish, assignmentId, assignmentFinish);
            }

            return(project);
        }
        private static ProjectData BackwardPass(this ProjectData project)
        {
            var computedTasks = new HashSet <TaskId>();
            var toBeScheduled = new Queue <TaskId>(project.Tasks);

            while (toBeScheduled.Count > 0)
            {
                var taskId                = toBeScheduled.Dequeue();
                var successors            = project.Get(TaskFields.SuccessorLinks, taskId);
                var allSuccessorsComputed = successors.All(l => computedTasks.Contains(l.SuccessorId));
                if (!allSuccessorsComputed)
                {
                    toBeScheduled.Enqueue(taskId);
                }
                else
                {
                    ComputeLateStartAndFinish(project, taskId, successors, out var lateStart, out var lateFinish);

                    project = project.SetRaw(TaskFields.LateStart, taskId, lateStart)
                              .SetRaw(TaskFields.LateFinish, taskId, lateFinish);

                    computedTasks.Add(taskId);
                }
            }

            return(project);
        }
 private static DateTimeOffset ComputeProjectFinish(ProjectData project)
 {
     return(project.Tasks
            .Select(t => project.Get(TaskFields.EarlyFinish, t))
            .DefaultIfEmpty(project.Information.Start)
            .Max());
 }
        private static ProjectData ForwardPass(this ProjectData project)
        {
            var computedTasks = new HashSet <TaskId>();
            var toBeScheduled = new Queue <TaskId>(project.Tasks);

            while (toBeScheduled.Count > 0)
            {
                var taskId                  = toBeScheduled.Dequeue();
                var predecessors            = project.Get(TaskFields.PredecessorLinks, taskId);
                var allPredecessorsComputed = predecessors.All(l => computedTasks.Contains(l.PredecessorId));
                if (!allPredecessorsComputed)
                {
                    toBeScheduled.Enqueue(taskId);
                }
                else
                {
                    ComputeEarlyStartAndFinish(project, taskId, predecessors, out var earlyStart, out var earlyFinish);

                    project = project.SetRaw(TaskFields.EarlyStart, taskId, earlyStart)
                              .SetRaw(TaskFields.EarlyFinish, taskId, earlyFinish);

                    computedTasks.Add(taskId);
                }
            }

            var projectFinish = ComputeProjectFinish(project);
            var information   = project.Information.WithFinish(projectFinish);

            return(project.WithInformation(information));
        }
        private static TimeSpan GetFreeSlack(ProjectData project, TaskId taskId)
        {
            var successors = project.Get(TaskFields.SuccessorLinks, taskId);

            if (successors.Any())
            {
                return(successors.Select(l => GetFreeSlack(project, l)).Min());
            }
            else
            {
                var calendar      = project.Information.Calendar;
                var earlyFinish   = project.Get(TaskFields.EarlyFinish, taskId);
                var projectFinish = project.Information.Finish;
                return(calendar.GetWork(earlyFinish, projectFinish));
            }
        }
Exemplo n.º 9
0
        private static ProjectData SetAssignmentUnits(ProjectData project, AssignmentId id, double value)
        {
            var taskId = project.Get(AssignmentFields.TaskId, id);

            return(project.SetRaw(AssignmentFields.Units, id, value)
                   .Reset(TaskFields.ResourceNames, taskId)
                   .Reset(TaskFields.ResourceInitials, taskId));
        }
        private static ProjectData SetTaskWork(ProjectData project, TaskId id, TimeSpan value)
        {
            project = project.SetRaw(TaskFields.Work, id, value);

            if (value == TimeSpan.Zero)
            {
                foreach (var assignmentId in project.GetAssignments(id))
                {
                    project = project.SetRaw(AssignmentFields.Work, assignmentId, TimeSpan.Zero);
                }
            }
            else
            {
                var totalExistingWork = TimeSpan.Zero;

                foreach (var assignmentId in project.GetAssignments(id))
                {
                    totalExistingWork += project.Get(AssignmentFields.Work, assignmentId);
                }

                var assignmentCount = project.GetAssignments(id).Count();

                foreach (var assignmentId in project.GetAssignments(id))
                {
                    var assignmentWork = project.Get(AssignmentFields.Work, assignmentId);

                    double newHours;

                    if (totalExistingWork > TimeSpan.Zero)
                    {
                        newHours = assignmentWork.TotalHours / totalExistingWork.TotalHours * value.TotalHours;
                    }
                    else
                    {
                        newHours = value.TotalHours / assignmentCount;
                    }

                    var newWork = TimeSpan.FromHours(newHours);

                    project = project.SetRaw(AssignmentFields.Work, assignmentId, newWork);
                }
            }

            return(project);
        }
Exemplo n.º 11
0
        private static TimeSpan GetFreeSlack(ProjectData project, TaskLink link)
        {
            var calendar = project.Information.Calendar;

            var projectStart           = project.Information.Start;
            var predecessorEarlyStart  = project.Get(TaskFields.EarlyStart, link.PredecessorId);
            var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, link.PredecessorId);
            var successorEarlyStart    = project.Get(TaskFields.EarlyStart, link.SuccessorId);
            var successorEarlyFinish   = project.Get(TaskFields.EarlyFinish, link.SuccessorId);

            switch (link.Type)
            {
            case TaskLinkType.FinishToStart:
                return(calendar.GetWork(predecessorEarlyFinish, successorEarlyStart) - link.Lag);

            case TaskLinkType.StartToStart:
                return(calendar.GetWork(predecessorEarlyStart, successorEarlyStart) - link.Lag);

            case TaskLinkType.FinishToFinish:
            {
                // Finish-to-Finish is special in that it won't schedule tasks before the project's
                // start date. Thus, we'll ignore the lag if the successor was supposed to end before
                // us (i.e. had a negative slack) but ended up ending after ours (i.e. it couldn't
                // start any earlier).

                var successorCannotStartAnyEarlier = successorEarlyStart == projectStart &&
                                                     successorEarlyFinish > predecessorEarlyFinish &&
                                                     link.Lag < TimeSpan.Zero;
                if (successorCannotStartAnyEarlier)
                {
                    return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish));
                }

                return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish) - link.Lag);
            }

            case TaskLinkType.StartToFinish:
                return(calendar.GetWork(predecessorEarlyStart, successorEarlyFinish) - link.Lag);

            default:
                throw new Exception($"Unexpected case label {link.Type}");
            }
        }
        private static ProjectData SetTaskDuration(ProjectData project, TaskId id, TimeSpan value)
        {
            project = project.SetRaw(TaskFields.Duration, id, value);

            var taskFinish = project.Get(TaskFields.Finish, id);

            foreach (var assignmentId in project.GetAssignments(id))
            {
                var assignmentFinish = project.Get(AssignmentFields.Finish, assignmentId);
                var assignmentUnits  = project.Get(AssignmentFields.Units, assignmentId);

                if (assignmentFinish == taskFinish)
                {
                    var assignmentWork = TimeSpan.FromHours(value.TotalHours * assignmentUnits);
                    project = project.Set(AssignmentFields.Work, assignmentId, assignmentWork);
                }
            }

            return(project);
        }
Exemplo n.º 13
0
        private static void ComputeLateStartAndFinish(ProjectData project, TaskId taskId, TaskLink successorLink, out DateTimeOffset lateStart, out DateTimeOffset lateFinish)
        {
            var calendar = project.Information.Calendar;

            switch (successorLink.Type)
            {
            case TaskLinkType.FinishToStart:
                lateFinish = project.Get(TaskFields.LateStart, successorLink.SuccessorId);
                break;

            case TaskLinkType.StartToStart:
                lateStart = project.Get(TaskFields.LateStart, successorLink.SuccessorId);
                ComputeFinish(project, taskId, ref lateStart, out lateFinish);
                break;

            case TaskLinkType.FinishToFinish:
                lateFinish = project.Get(TaskFields.LateFinish, successorLink.SuccessorId);
                break;

            case TaskLinkType.StartToFinish:
                lateStart = project.Get(TaskFields.LateFinish, successorLink.SuccessorId);
                ComputeFinish(project, taskId, ref lateStart, out lateFinish);
                break;

            default:
                throw new Exception($"Unexpected case label {successorLink.Type}");
            }

            if (successorLink.Lag != TimeSpan.Zero)
            {
                lateFinish = project.Information.Calendar.AddWork(lateFinish, -successorLink.Lag);
            }

            // TODO: Is this correct? We probably need to align with with ComputeEarlyStart.
            if (lateFinish > project.Information.Finish)
            {
                lateFinish = project.Information.Finish;
            }

            ComputeStart(project, taskId, out lateStart, ref lateFinish);
        }
        private static ProjectData SetTaskOrdinal(ProjectData project, TaskId id, int value)
        {
            if (value < 0 || value >= project.TaskMap.Count)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            var oldOrdinal = project.Get(TaskFields.Ordinal, id);
            var newOrdinal = value;

            var orderedTaskIds = project.Tasks
                                 .OrderBy(t => project.Get(TaskFields.Ordinal, t))
                                 .ToList();

            orderedTaskIds.RemoveAt(oldOrdinal);
            orderedTaskIds.Insert(newOrdinal, id);

            // First we update all ordinals

            for (var i = 0; i < orderedTaskIds.Count; i++)
            {
                var taskId  = orderedTaskIds[i];
                var ordinal = i;

                project = project.SetRaw(TaskFields.Ordinal, taskId, ordinal);
            }

            // Then we can update all predecessors/successors

            foreach (var taskId in orderedTaskIds)
            {
                project = project.Reset(TaskFields.Predecessors, taskId)
                          .Reset(TaskFields.Successors, taskId);
            }

            return(project);
        }
        private static ProjectData SetTaskPredecessorsOrSuccessors(ProjectData project, TaskId id, string value, bool isSuccessors)
        {
            var linkField = isSuccessors ? TaskFields.SuccessorLinks : TaskFields.PredecessorLinks;

            value = value.Trim();

            var predecessorsBuilder = ImmutableArray.CreateBuilder <TaskId>();
            var remainingTaskLinks  = project.Get(linkField, id).ToList();

            if (value.Length > 0)
            {
                var parts = value.Split(',');

                foreach (var part in parts)
                {
                    var partText = part.Trim();

                    var taskText = partText;
                    var sign     = Math.Max(partText.IndexOf('+'), partText.IndexOf('-'));
                    var lag      = TimeSpan.Zero;

                    if (sign >= 0)
                    {
                        var lagText = partText.Substring(sign).Trim();
                        lag      = project.Information.TimeConversion.ParseDuration(lagText);
                        taskText = partText.Substring(0, sign).Trim();
                    }

                    var taskOrdinalText = taskText;
                    var linkType        = (TaskLinkType?)null;

                    if (taskText.EndsWith("FS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else if (taskText.EndsWith("SS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToStart;
                    }
                    else if (taskText.EndsWith("FF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToFinish;
                    }
                    else if (taskText.EndsWith("SF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToFinish;
                    }

                    if (linkType != null)
                    {
                        taskOrdinalText = taskText.Substring(0, taskText.Length - 2).Trim();
                    }
                    else if (sign < 0)
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else
                    {
                        throw new FormatException($"'{partText}' isn't a valid value");
                    }

                    if (!int.TryParse(taskOrdinalText, out var taskOrdinal))
                    {
                        throw new FormatException($"'{taskOrdinalText}' isn't a valid int");
                    }

                    var taskId = project.GetTask(taskOrdinal);
                    if (taskId.IsDefault)
                    {
                        throw new FormatException($"'{taskOrdinal}' isn't a valid task");
                    }

                    var predecessorId = isSuccessors ? id : taskId;
                    var successorId   = isSuccessors ? taskId : id;

                    var taskLink = TaskLink.Create(predecessorId, successorId, linkType.Value, lag);

                    var existingLink = project.GetTaskLink(predecessorId, successorId);
                    if (existingLink != null)
                    {
                        project = project.RemoveTaskLink(existingLink);
                    }
                    else if (project.TaskLinkCausesCycle(taskLink))
                    {
                        var predecessorOrdinal = project.Get(TaskFields.Ordinal, predecessorId);
                        var successorOrdinal   = project.Get(TaskFields.Ordinal, successorId);
                        throw new InvalidOperationException($"Cannot add a link from task {predecessorOrdinal} to task {successorOrdinal} as this would cause a cycle.");
                    }

                    project = project.AddTaskLink(taskLink);

                    remainingTaskLinks.Remove(taskLink);
                }
            }

            foreach (var taskLink in remainingTaskLinks)
            {
                project = project.RemoveTaskLink(taskLink);
            }

            return(project);
        }