public ProjectData RemoveTaskLink(TaskLink taskLink)
        {
            Debug.Assert(taskLink != null);

            if (!TaskMap.ContainsKey(taskLink.PredecessorId) || !TaskMap.ContainsKey(taskLink.SuccessorId))
            {
                return(this);
            }

            var project = this;

            var predescessorLinks = Get(TaskFields.PredecessorLinks, taskLink.SuccessorId);

            predescessorLinks = predescessorLinks.Remove(taskLink);

            var successorLinks = Get(TaskFields.SuccessorLinks, taskLink.PredecessorId);

            successorLinks = successorLinks.Remove(taskLink);

            project = project.SetRaw(TaskFields.PredecessorLinks, taskLink.SuccessorId, predescessorLinks)
                      .SetRaw(TaskFields.SuccessorLinks, taskLink.PredecessorId, successorLinks)
                      .Reset(TaskFields.Predecessors, taskLink.SuccessorId)
                      .Reset(TaskFields.Successors, taskLink.PredecessorId);

            return(project);
        }
Пример #2
0
        public Project AddTaskLink(TaskLink taskLink)
        {
            if (taskLink == null)
            {
                throw new ArgumentNullException(nameof(taskLink));
            }

            var predecessor = GetTask(taskLink.PredecessorId);

            if (predecessor == null)
            {
                throw new ArgumentException($"The project doesn't contain a task with the predecessor ID {taskLink.PredecessorId}", nameof(taskLink));
            }

            var successor = GetTask(taskLink.SuccessorId);

            if (successor == null)
            {
                throw new ArgumentException($"The project doesn't contain a task with the successor ID {taskLink.SuccessorId}", nameof(taskLink));
            }

            if (Data.TaskLinkCausesCycle(taskLink))
            {
                throw new InvalidOperationException($"Cannot add a link from task {predecessor.Ordinal} to task {successor.Ordinal} as this would cause a cycle.");
            }

            var project = Data.AddTaskLink(taskLink);

            return(UpdateProject(project));
        }
Пример #3
0
        public Project AddTaskLink(TaskId predecessorId,
                                   TaskId successorId,
                                   TaskLinkType type = TaskLinkType.FinishToStart,
                                   TimeSpan lag      = default)
        {
            var taskLink = TaskLink.Create(predecessorId, successorId, type, lag);

            return(AddTaskLink(taskLink));
        }
        public bool TaskLinkCausesCycle(TaskLink taskLink)
        {
            Debug.Assert(taskLink != null);

            if (!TaskMap.ContainsKey(taskLink.PredecessorId) || !TaskMap.ContainsKey(taskLink.SuccessorId))
            {
                return(false);
            }

            IEnumerable <TaskId> GetPredecessors(TaskId successorId)
            {
                var links = Get(TaskFields.PredecessorLinks, successorId);

                foreach (var link in links)
                {
                    yield return(link.PredecessorId);
                }

                if (successorId == taskLink.SuccessorId)
                {
                    yield return(taskLink.PredecessorId);
                }
            }

            var queue = new Queue <TaskId>();

            foreach (var id in Tasks)
            {
                foreach (var predecessorId in GetPredecessors(id))
                {
                    queue.Enqueue(predecessorId);

                    while (queue.Count > 0)
                    {
                        var taskId = queue.Dequeue();
                        if (taskId == id)
                        {
                            return(true);
                        }

                        foreach (var pid in GetPredecessors(taskId))
                        {
                            queue.Enqueue(pid);
                        }

                        if (taskId == taskLink.SuccessorId)
                        {
                            queue.Enqueue(taskLink.PredecessorId);
                        }
                    }
                }
            }

            return(false);
        }
Пример #5
0
        public Project RemoveTaskLink(TaskLink taskLink)
        {
            if (taskLink == null)
            {
                throw new ArgumentNullException(nameof(taskLink));
            }

            var project = Data.RemoveTaskLink(taskLink);

            return(UpdateProject(project));
        }
        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}");
            }
        }
        public ProjectData AddTaskLink(TaskLink taskLink)
        {
            Debug.Assert(taskLink != null);
            Debug.Assert(GetTaskLink(taskLink.PredecessorId, taskLink.SuccessorId) == null);
            Debug.Assert(!TaskLinkCausesCycle(taskLink));

            var project = this;

            var predescessorLinks = Get(TaskFields.PredecessorLinks, taskLink.SuccessorId);

            predescessorLinks = InsertTaskLink(predescessorLinks, taskLink, isPredecessor: true);

            var successorLinks = Get(TaskFields.SuccessorLinks, taskLink.PredecessorId);

            successorLinks = InsertTaskLink(successorLinks, taskLink, isPredecessor: false);

            project = project.SetRaw(TaskFields.PredecessorLinks, taskLink.SuccessorId, predescessorLinks)
                      .SetRaw(TaskFields.SuccessorLinks, taskLink.PredecessorId, successorLinks)
                      .Reset(TaskFields.Predecessors, taskLink.SuccessorId)
                      .Reset(TaskFields.Successors, taskLink.PredecessorId);

            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);
        }
        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 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);
            }
        }
        private ImmutableArray <TaskLink> InsertTaskLink(ImmutableArray <TaskLink> taskLinks, TaskLink taskLink, bool isPredecessor)
        {
            var newId      = isPredecessor ? taskLink.PredecessorId : taskLink.SuccessorId;
            var newOrdinal = Get(TaskFields.Ordinal, newId);

            for (var i = 0; i < taskLinks.Length; i++)
            {
                var existingId      = isPredecessor ? taskLinks[i].PredecessorId : taskLinks[i].SuccessorId;
                var existingOrdinal = Get(TaskFields.Ordinal, existingId);

                if (newOrdinal > existingOrdinal)
                {
                    return(taskLinks.Insert(i, taskLink));
                }
            }

            return(taskLinks.Add(taskLink));
        }