You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1385 lines
53 KiB
1385 lines
53 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace SunlightCentralizedControlManagement_SCCM_.UserClass
|
|
{
|
|
/// <summary>
|
|
/// Wrapper ProjectManager class
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ProjectManager : ProjectManager<IP_Task, object>
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concrete ProjectManager class for the IProjectManager interface
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <typeparam name="R"></typeparam>
|
|
[Serializable]
|
|
public class ProjectManager<T, R> : IProjectManager<T, R>
|
|
where T : IP_Task
|
|
where R : class
|
|
{
|
|
HashSet<T> _mRegister = new HashSet<T>();
|
|
List<T> _mRootIP_Tasks = new List<T>();
|
|
Dictionary<T, List<T>> _mMembersOfGroup = new Dictionary<T, List<T>>(); // Map group to list of members
|
|
Dictionary<T, HashSet<T>> _mDependantsOfPrecedent = new Dictionary<T, HashSet<T>>(); // Map precendent to list of dependents
|
|
Dictionary<T, HashSet<R>> _mResourcesOfIP_Task = new Dictionary<T, HashSet<R>>(); // Map IP_Task to list of resources
|
|
Dictionary<T, List<T>> _mPartsOfSplitIP_Task = new Dictionary<T, List<T>>(); // Map split IP_Task to list of IP_Task parts
|
|
Dictionary<T, T> _mSplitIP_TaskOfPart = new Dictionary<T, T>(); // Map a IP_Task part to the original split IP_Task
|
|
Dictionary<T, T> _mGroupOfMember = new Dictionary<T, T>(); // Map member IP_Task to parent group IP_Task
|
|
Dictionary<T, int> _mIP_TaskIndices = new Dictionary<T, int>(); // Map the IP_Task to its zero-based index order position
|
|
|
|
/// <summary>
|
|
/// Create a new Project
|
|
/// </summary>
|
|
public ProjectManager()
|
|
{
|
|
Now = TimeSpan.Zero;
|
|
Start = DateTime.Now;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or set the TimeSpan we are at now from Start DateTime
|
|
/// </summary>
|
|
public TimeSpan Now { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get or set the starting date for this project
|
|
/// </summary>
|
|
public DateTime Start { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get the date after the specified TimeSpan
|
|
/// </summary>
|
|
/// <param name="span"></param>
|
|
/// <returns></returns>
|
|
public DateTime GetDateTime(TimeSpan span)
|
|
{
|
|
return this.Start.Add(span);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new T for this Project and add it to the T tree
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public void Add(T IP_Task)
|
|
{
|
|
if (!this._mRegister.Contains(IP_Task))
|
|
{
|
|
_mRegister.Add(IP_Task);
|
|
_mRootIP_Tasks.Add(IP_Task);
|
|
_mMembersOfGroup[IP_Task] = new List<T>();
|
|
_mDependantsOfPrecedent[IP_Task] = new HashSet<T>();
|
|
_mResourcesOfIP_Task[IP_Task] = new HashSet<R>();
|
|
_mGroupOfMember[IP_Task] = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove IP_Task from this Project
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
public void Delete(T IP_Task)
|
|
{
|
|
if (IP_Task != null
|
|
&& !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // not a IP_Task part
|
|
)
|
|
{
|
|
// Check if is group so can ungroup the IP_Task
|
|
if (this.IsGroup(IP_Task))
|
|
this.Ungroup(IP_Task);
|
|
|
|
if (this.IsSplit(IP_Task))
|
|
this.Merge(IP_Task);
|
|
|
|
// Really delete all references
|
|
_mRootIP_Tasks.Remove(IP_Task);
|
|
_mMembersOfGroup.Remove(IP_Task);
|
|
_mDependantsOfPrecedent.Remove(IP_Task);
|
|
_mResourcesOfIP_Task.Remove(IP_Task);
|
|
_mGroupOfMember.Remove(IP_Task);
|
|
_mPartsOfSplitIP_Task.Remove(IP_Task);
|
|
foreach (var g in _mMembersOfGroup) g.Value.Remove(IP_Task); // optimised: no need to check for contains
|
|
foreach (var g in _mDependantsOfPrecedent) g.Value.Remove(IP_Task);
|
|
_mRegister.Remove(IP_Task);
|
|
}
|
|
else if (IP_Task != null
|
|
&& _mSplitIP_TaskOfPart.ContainsKey(IP_Task) // must be existing part
|
|
)
|
|
{
|
|
var split = _mSplitIP_TaskOfPart[IP_Task];
|
|
var parts = _mPartsOfSplitIP_Task[split];
|
|
if (parts.Count > 2)
|
|
{
|
|
parts.Remove(IP_Task); // remove the part from the split IP_Task
|
|
_mRegister.Remove(IP_Task); // unregister the part
|
|
_mResourcesOfIP_Task.Remove(IP_Task);
|
|
_mSplitIP_TaskOfPart.Remove(IP_Task); // remove the reverse lookup
|
|
|
|
split.Start = parts.First().Start; // recalculate the split IP_Task
|
|
split.End = parts.Last().End;
|
|
split.Duration = split.End - split.Start;
|
|
}
|
|
else
|
|
{
|
|
this.Merge(split);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the member T to the group T
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <param name="member"></param>
|
|
public void Group(T group, T member)
|
|
{
|
|
if (group != null
|
|
&& member != null
|
|
&& _mRegister.Contains(group)
|
|
)
|
|
{
|
|
// if the member is a IP_Task part, assign the split IP_Task to the group instead
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
|
|
|
|
if (_mRegister.Contains(member)
|
|
&& !group.Equals(member)
|
|
&& !_mPartsOfSplitIP_Task.ContainsKey(group) // group cannot be split IP_Task
|
|
&& !_mSplitIP_TaskOfPart.ContainsKey(group) // group cannot be parts
|
|
&& !this.MembersOf(member).Contains(group)
|
|
&& !this.HasRelations(group)
|
|
)
|
|
{
|
|
_DetachIP_Task(member);
|
|
|
|
// add member to new group
|
|
_mMembersOfGroup[group].Add(member);
|
|
_mGroupOfMember[member] = group;
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
// clear indices since positions changed
|
|
_mIP_TaskIndices.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the member IP_Task from its group
|
|
/// </summary>
|
|
public void Ungroup(T group, T member)
|
|
{
|
|
if (group != null
|
|
&& member != null
|
|
&& _mRegister.Contains(group)
|
|
)
|
|
{
|
|
// change the member to become the split IP_Task is member is a IP_Task part
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
|
|
if (_mRegister.Contains(member) && this.IsGroup(group))
|
|
{
|
|
var ancestor = this.GroupsOf(group).LastOrDefault();
|
|
if(ancestor == null) // group is in root
|
|
_mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(group) + 1, member);
|
|
else // group is not in root, we get the ancestor that is in root
|
|
_mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(ancestor) + 1, member);
|
|
_mMembersOfGroup[group].Remove(member);
|
|
_mGroupOfMember[member] = null;
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ungroup all member IP_Task under the specfied group IP_Task. The specified group IP_Task will become a normal IP_Task.
|
|
/// If the there is a grandparent group, the members will become members of the grandparent group.
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
public void Ungroup(T group)
|
|
{
|
|
if (group != null
|
|
//&& _mRegister.Contains(group)
|
|
&& _mMembersOfGroup.TryGetValue(group, out List<T> members))
|
|
{
|
|
var newgroup = this.DirectGroupOf(group);
|
|
if (newgroup == null)
|
|
{
|
|
foreach (var member in members)
|
|
{
|
|
_mRootIP_Tasks.Add(member);
|
|
_mGroupOfMember[member] = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var member in members)
|
|
{
|
|
_mMembersOfGroup[newgroup].Add(member);
|
|
_mGroupOfMember[member] = null;
|
|
}
|
|
}
|
|
|
|
members.Clear();
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the zero-based index of the IP_Task in this Project
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public int IndexOf(T IP_Task)
|
|
{
|
|
if (_mRegister.Contains(IP_Task))
|
|
{
|
|
if (_mIP_TaskIndices.ContainsKey(IP_Task))
|
|
return _mIP_TaskIndices[IP_Task];
|
|
|
|
int i = 0;
|
|
foreach (var x in IP_Tasks)
|
|
{
|
|
if (x.Equals(IP_Task))
|
|
{
|
|
_mIP_TaskIndices[IP_Task] = i;
|
|
return i;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Re-order position of the IP_Task by offset amount of places
|
|
/// If IP_Task is moved between members, the IP_Task is added to the members' group
|
|
/// If IP_Task is a member and it is moved above it's group or below last sibling member, then it is moved out of its group
|
|
/// If IP_Task is a part, then its parent split-IP_Task will be move instead
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="offset"></param>
|
|
public void Move(T IP_Task, int offset)
|
|
{
|
|
if (IP_Task != null && _mRegister.Contains(IP_Task) && offset != 0)
|
|
{
|
|
if (IsPart(IP_Task)) IP_Task = SplitIP_TaskOf(IP_Task);
|
|
|
|
int indexofIP_Task = IndexOf(IP_Task);
|
|
if (indexofIP_Task > -1)
|
|
{
|
|
int newindexofIP_Task = indexofIP_Task + offset;
|
|
// check for out of index bounds
|
|
var IP_Taskcount = IP_Tasks.Count();
|
|
if (newindexofIP_Task < 0) newindexofIP_Task = 0;
|
|
else if (newindexofIP_Task > IP_Taskcount) newindexofIP_Task = IP_Taskcount;
|
|
// get the index of the IP_Task that will be displaced
|
|
var displacedIP_Task = IP_Tasks.ElementAtOrDefault(newindexofIP_Task);
|
|
|
|
if(displacedIP_Task == IP_Task)
|
|
{
|
|
return;
|
|
}
|
|
if (displacedIP_Task == null)
|
|
{
|
|
// adding to the end of the IP_Task list
|
|
_DetachIP_Task(IP_Task);
|
|
_mRootIP_Tasks.Add(IP_Task);
|
|
}
|
|
else if (!displacedIP_Task.Equals(IP_Task))
|
|
{
|
|
int indexofdestinationIP_Task;
|
|
var displacedIP_Taskparent = this.DirectGroupOf(displacedIP_Task);
|
|
if (displacedIP_Taskparent == null) // displacedIP_Task is in root
|
|
{
|
|
indexofdestinationIP_Task = _mRootIP_Tasks.IndexOf(displacedIP_Task);
|
|
_DetachIP_Task(IP_Task);
|
|
_mRootIP_Tasks.Insert(indexofdestinationIP_Task, IP_Task);
|
|
}
|
|
else if (!displacedIP_Taskparent.Equals(IP_Task)) // displaced IP_Task is not under the moving IP_Task
|
|
{
|
|
var memberlist = _mMembersOfGroup[displacedIP_Taskparent];
|
|
indexofdestinationIP_Task = memberlist.IndexOf(displacedIP_Task);
|
|
_DetachIP_Task(IP_Task);
|
|
memberlist.Insert(indexofdestinationIP_Task, IP_Task);
|
|
_mGroupOfMember[IP_Task] = displacedIP_Taskparent;
|
|
}
|
|
}
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
|
|
// clear indices since positions changed
|
|
_mIP_TaskIndices.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the T tree
|
|
/// </summary>
|
|
public IEnumerable<T> IP_Tasks
|
|
{
|
|
get
|
|
{
|
|
var stack = new Stack<T>(1024);
|
|
var rstack = new Stack<T>(30);
|
|
foreach (var IP_Task in _mRootIP_Tasks)
|
|
{
|
|
stack.Push(IP_Task);
|
|
while (stack.Count > 0)
|
|
{
|
|
var visited = stack.Pop();
|
|
yield return visited;
|
|
|
|
foreach (var member in _mMembersOfGroup[visited])
|
|
rstack.Push(member);
|
|
|
|
while (rstack.Count > 0) stack.Push(rstack.Pop());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate upwards from member to and through all the parents and grandparents of the specified IP_Task
|
|
/// </summary>
|
|
public IEnumerable<T> GroupsOf(T member)
|
|
{
|
|
T parent = DirectGroupOf(member);
|
|
while (parent != null)
|
|
{
|
|
yield return parent;
|
|
parent = DirectGroupOf(parent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the children and grandchildren of the specified group
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> MembersOf(T group)
|
|
{
|
|
if (_mRegister.Contains(group))
|
|
{
|
|
Stack<T> stack = new Stack<T>(20);
|
|
Stack<T> rstack = new Stack<T>(10);
|
|
foreach (var child in _mMembersOfGroup[group])
|
|
{
|
|
stack.Push(child);
|
|
while (stack.Count > 0)
|
|
{
|
|
var visitedchild = stack.Pop();
|
|
yield return visitedchild;
|
|
|
|
// push the grandchild
|
|
rstack.Clear();
|
|
foreach (var grandchild in _mMembersOfGroup[visitedchild])
|
|
rstack.Push(grandchild);
|
|
|
|
// put in the right visiting order
|
|
while (rstack.Count > 0)
|
|
stack.Push(rstack.Pop());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the parent group of the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="member"></param>
|
|
/// <returns></returns>
|
|
public T DirectGroupOf(T member)
|
|
{
|
|
if (_mGroupOfMember.ContainsKey(member)) // _mRegister.Contains(IP_Task))
|
|
{
|
|
return _mGroupOfMember[member];
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the direct children of the specified group
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> DirectMembersOf(T group)
|
|
{
|
|
if (group == null) yield break;
|
|
|
|
if (_mMembersOfGroup.TryGetValue(group, out List<T> list))
|
|
{
|
|
var iter = list.GetEnumerator();
|
|
while (iter.MoveNext()) yield return iter.Current;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the direct precedents and indirect precedents of the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="dependant"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> PrecedentsOf(T dependant)
|
|
{
|
|
if (_mRegister.Contains(dependant))
|
|
{
|
|
var stack = new Stack<T>(20);
|
|
foreach (var p in DirectPrecedentsOf(dependant))
|
|
{
|
|
stack.Push(p);
|
|
while (stack.Count > 0)
|
|
{
|
|
var visited = stack.Pop();
|
|
yield return visited;
|
|
foreach (var grandp in DirectPrecedentsOf(visited))
|
|
stack.Push(grandp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the direct dependants and indirect dependants of the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="precendent"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> DependantsOf(T precendent)
|
|
{
|
|
if (!_mDependantsOfPrecedent.ContainsKey(precendent)) yield break;
|
|
|
|
var stack = new Stack<T>(20);
|
|
foreach (var d in _mDependantsOfPrecedent[precendent])
|
|
{
|
|
stack.Push(d);
|
|
while (stack.Count > 0)
|
|
{
|
|
var visited = stack.Pop();
|
|
yield return visited;
|
|
foreach (var grandd in _mDependantsOfPrecedent[visited])
|
|
stack.Push(grandd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the direct precedents of the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="dependants"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> DirectPrecedentsOf(T dependants)
|
|
{
|
|
return _mDependantsOfPrecedent.Where(x => x.Value.Contains(dependants)).Select(x => x.Key);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the direct dependants of the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="precedent"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> DirectDependantsOf(T precedent)
|
|
{
|
|
if (precedent == null) yield break;
|
|
|
|
if (_mDependantsOfPrecedent.TryGetValue(precedent, out HashSet<T> dependants))
|
|
{
|
|
var iter = dependants.GetEnumerator();
|
|
while (iter.MoveNext()) yield return iter.Current;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all IP_Tasks that is a precedent, having dependants.
|
|
/// </summary>
|
|
public IEnumerable<T> Precedents
|
|
{
|
|
get { return _mDependantsOfPrecedent.Where(x => _mDependantsOfPrecedent[x.Key].Count > 0).Select(x => x.Key); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate list of critical paths in Project
|
|
/// </summary>
|
|
public IEnumerable<IEnumerable<T>> CriticalPaths
|
|
{
|
|
get
|
|
{
|
|
Dictionary<TimeSpan, List<T>> endtimelookp = new Dictionary<TimeSpan, List<T>>(1024);
|
|
TimeSpan max_end = TimeSpan.MinValue;
|
|
foreach (var IP_Task in this.IP_Tasks)
|
|
{
|
|
if (!endtimelookp.TryGetValue(IP_Task.End, out List<T> list))
|
|
endtimelookp[IP_Task.End] = new List<T>(10);
|
|
endtimelookp[IP_Task.End].Add(IP_Task);
|
|
|
|
if (IP_Task.End > max_end) max_end = IP_Task.End;
|
|
}
|
|
|
|
if (max_end != TimeSpan.MinValue)
|
|
{
|
|
foreach (var IP_Task in endtimelookp[max_end])
|
|
{
|
|
yield return new T[] { IP_Task }.Concat(PrecedentsOf(IP_Task));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether the specified IP_Task is a group
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public bool IsGroup(T IP_Task)
|
|
{
|
|
if (_mMembersOfGroup.TryGetValue(IP_Task, out List<T> list))
|
|
return list.Count > 0;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether the specified IP_Task is a member
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public bool IsMember(T IP_Task)
|
|
{
|
|
return this.DirectGroupOf(IP_Task) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether the specified IP_Task has relations, either has dependants or has precedents connecting to it.
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public bool HasRelations(T IP_Task)
|
|
{
|
|
if (_mRegister.Contains(IP_Task) && _mDependantsOfPrecedent.ContainsKey(IP_Task))
|
|
{
|
|
return _mDependantsOfPrecedent[IP_Task].Count > 0 || DirectPrecedentsOf(IP_Task).FirstOrDefault() != null;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a relation between the precedent and dependant IP_Task
|
|
/// </summary>
|
|
/// <param name="precedent"></param>
|
|
/// <param name="dependant"></param>
|
|
public void Relate(T precedent, T dependant)
|
|
{
|
|
if (_mRegister.Contains(precedent)
|
|
&& _mRegister.Contains(dependant)
|
|
)
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
|
|
|
|
if (!precedent.Equals(dependant)
|
|
&&!this.DependantsOf(dependant).Contains(precedent)
|
|
&& !this.IsGroup(precedent)
|
|
&& !this.IsGroup(dependant)
|
|
)
|
|
{
|
|
_mDependantsOfPrecedent[precedent].Add(dependant);
|
|
|
|
_RecalculateDependantsOf(precedent);
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unset the relation between the precedent and dependant IP_Task, if any.
|
|
/// </summary>
|
|
/// <param name="precedent"></param>
|
|
/// <param name="dependant"></param>
|
|
public void Unrelate(T precedent, T dependant)
|
|
{
|
|
if (_mRegister.Contains(precedent) && _mRegister.Contains(dependant))
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
|
|
|
|
_mDependantsOfPrecedent[precedent].Remove(dependant);
|
|
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all dependant IP_Task from specified precedent IP_Task
|
|
/// </summary>
|
|
/// <param name="precedent"></param>
|
|
public void Unrelate(T precedent)
|
|
{
|
|
if (_mRegister.Contains(precedent))
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(precedent))
|
|
precedent = _mSplitIP_TaskOfPart[precedent];
|
|
|
|
_mDependantsOfPrecedent[precedent].Clear();
|
|
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assign the specified resource to the specified IP_Task
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="resource"></param>
|
|
public void Assign(T IP_Task, R resource)
|
|
{
|
|
if (_mRegister.Contains(IP_Task) && !_mResourcesOfIP_Task[IP_Task].Contains(resource))
|
|
_mResourcesOfIP_Task[IP_Task].Add(resource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unassign the specified resource from the specfied IP_Task
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="resource"></param>
|
|
public void Unassign(T IP_Task, R resource)
|
|
{
|
|
_mResourcesOfIP_Task[IP_Task].Remove(resource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unassign the all resources from the specfied IP_Task
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
public void Unassign(T IP_Task)
|
|
{
|
|
if(_mRegister.Contains(IP_Task))
|
|
_mResourcesOfIP_Task[IP_Task].Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unassign the specified resource from all IP_Tasks that has this resource assigned
|
|
/// </summary>
|
|
/// <param name="resource"></param>
|
|
public void Unassign(R resource)
|
|
{
|
|
foreach (var r in _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)))
|
|
r.Value.Remove(resource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the resources that has been assigned to some IP_Task.
|
|
/// </summary>
|
|
public IEnumerable<R> Resources
|
|
{
|
|
get
|
|
{
|
|
return _mResourcesOfIP_Task.SelectMany(x => x.Value).Distinct();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the resources that has been assigned to the specified IP_Task.
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<R> ResourcesOf(T IP_Task)
|
|
{
|
|
if (IP_Task == null || !_mRegister.Contains(IP_Task))
|
|
yield break;
|
|
|
|
if (_mResourcesOfIP_Task.TryGetValue(IP_Task, out HashSet<R> list))
|
|
{
|
|
foreach (var item in list)
|
|
yield return item;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate through all the IP_Tasks that has the specified resource assigned to it.
|
|
/// </summary>
|
|
/// <param name="resource"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> IP_TasksOf(R resource)
|
|
{
|
|
return _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)).Select(x => x.Key);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the start value. Affects group start/end and dependants start time.
|
|
/// </summary>
|
|
public void SetStart(T IP_Task, TimeSpan value)
|
|
{
|
|
if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && !this.IsGroup(IP_Task))
|
|
{
|
|
_SetStartHelper(IP_Task, value);
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
}
|
|
// Set start for a group IP_Task
|
|
else if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && this.IsGroup(IP_Task))
|
|
{
|
|
_SetGroupStartHelper(IP_Task, value);
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the end time. Affects group end and dependants start time.
|
|
/// </summary>
|
|
public void SetEnd(T IP_Task, TimeSpan value)
|
|
{
|
|
if (_mRegister.Contains(IP_Task) && value != IP_Task.End && !this.IsGroup(IP_Task))
|
|
{
|
|
this._SetEndHelper(IP_Task, value);
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the duration of the specified IP_Task from start to end.
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="duration">Number of timescale units between ProjectManager.Start</param>
|
|
public void SetDuration(T IP_Task, TimeSpan duration)
|
|
{
|
|
this.SetEnd(IP_Task, IP_Task.Start + duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the percentage complete of the specified IP_Task from 0.0f to 1.0f.
|
|
/// No effect on group IP_Tasks as they will get the aggregated percentage complete of all child IP_Tasks
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="complete"></param>
|
|
public void SetComplete(T IP_Task, float complete)
|
|
{
|
|
if (_mRegister.Contains(IP_Task)
|
|
&& complete != IP_Task.Complete
|
|
&& !this.IsGroup(IP_Task) // not a group
|
|
&& !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // not a split IP_Task
|
|
)
|
|
{
|
|
_SetCompleteHelper(IP_Task, complete);
|
|
|
|
_RecalculateComplete();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set whether to collapse the specified group IP_Task. No effect on regular IP_Tasks.
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <param name="collasped"></param>
|
|
public void SetCollapse(T IP_Task, bool collasped)
|
|
{
|
|
if (_mRegister.Contains(IP_Task) && this.IsGroup(IP_Task))
|
|
{
|
|
IP_Task.IsCollapsed = collasped;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split the specified IP_Task into consecutive parts part1 and part2.
|
|
/// </summary>
|
|
/// <param name="IP_Task">The regular IP_Task to split which has duration of at least 2 to make two parts of 1 time unit duration each.</param>
|
|
/// <param name="part1">New IP_Task part (1) of the split IP_Task, with the start time of the original IP_Task and the specified duration value.</param>
|
|
/// <param name="part2">New IP_Task part (2) of the split IP_Task, starting 1 time unit after part (1) ends and having the remaining of the duration of the origina IP_Task.</param>
|
|
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
|
|
public void Split(T IP_Task, T part1, T part2, TimeSpan duration)
|
|
{
|
|
if (IP_Task != null
|
|
&& part1 != null
|
|
&& part2 != null
|
|
&& !part1.Equals(part2) // parts cannot be the same
|
|
&& _mRegister.Contains(IP_Task) // IP_Task must be registered
|
|
&& !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // IP_Task must not already be a split IP_Task
|
|
&& !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // IP_Task must not be a IP_Task part
|
|
&& _mMembersOfGroup[IP_Task].Count == 0 // IP_Task cannot be a group
|
|
&& !_mRegister.Contains(part1) // part1 and part2 must have never existed
|
|
&& !_mRegister.Contains(part2)
|
|
)
|
|
{
|
|
_mRegister.Add(part1); // register part1
|
|
_mResourcesOfIP_Task[part1] = new HashSet<R>(); // create container for holding resource
|
|
|
|
// add part1 to split IP_Task
|
|
IP_Task.Complete = 0.0f; // reset the complete status
|
|
var parts = _mPartsOfSplitIP_Task[IP_Task] = new List<T>(2);
|
|
parts.Add(part1);
|
|
_mSplitIP_TaskOfPart[part1] = IP_Task; // make a reverse lookup
|
|
|
|
// allign the schedule
|
|
if (duration <= TimeSpan.Zero || duration >= IP_Task.Duration) duration = TimeSpan.FromTicks(IP_Task.Duration.Ticks / 2);
|
|
part1.Start = IP_Task.Start;
|
|
part1.End = IP_Task.End;
|
|
part1.Duration = IP_Task.Duration;
|
|
|
|
// split part1 to give part2
|
|
this.Split(part1, part2, duration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split the specified part and obtain another part from it.
|
|
/// </summary>
|
|
/// <param name="part">The IP_Task part to split which has duration of at least 2 to make two parts of 1 time unit duration each. Its duration will be set to the specified duration value.</param>
|
|
/// <param name="other">New IP_Task part of the original part, starting 1 time unit after it ends and having the remaining of the duration of the original part.</param>
|
|
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
|
|
public void Split(T part, T other, TimeSpan duration)
|
|
{
|
|
if (part != null
|
|
&& other != null
|
|
&& _mSplitIP_TaskOfPart.ContainsKey(part) // part must be an existing part
|
|
&& !_mRegister.Contains(other) // other must not have existed
|
|
)
|
|
{
|
|
_mRegister.Add(other); // register other part
|
|
_mResourcesOfIP_Task[other] = new HashSet<R>(); // create container for holding resource
|
|
|
|
var split = _mSplitIP_TaskOfPart[part]; // get the split IP_Task
|
|
var parts = _mPartsOfSplitIP_Task[split]; // get the list of ordered parts
|
|
|
|
parts.Insert(parts.IndexOf(part) + 1, other); // insert the other part after the existing part
|
|
_mSplitIP_TaskOfPart[other] = split; // set the reverse lookup
|
|
|
|
System.Diagnostics.Debug.Write("Project::Split(T part, T other, TimeSpan duration): Need to define minimum duration for splitting.");
|
|
|
|
// limit the duration point within the split IP_Task duration
|
|
if (duration <= TimeSpan.Zero || duration >= part.Duration) duration = TimeSpan.FromTicks(part.Duration.Ticks / 2);
|
|
|
|
// the real split
|
|
var one_duration = duration;
|
|
var two_duration = part.Duration - duration;
|
|
part.Duration = one_duration;
|
|
part.End = part.Start + one_duration;
|
|
other.Duration = two_duration;
|
|
other.Start = part.End;
|
|
other.End = other.Start + two_duration;
|
|
|
|
_PackPartsForward(parts);
|
|
split.Start = parts.First().Start; // recalculate the split IP_Task
|
|
split.End = parts.Last().End;
|
|
split.Duration = split.End - split.Start;
|
|
|
|
_RecalculateDependantsOf(split);
|
|
_RecalculateAncestorsSchedule();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Join part1 and part2 in a split IP_Task into a single part represented by part1, and part2 will be deleted from the ProjectManager.
|
|
/// The resulting part will have a duration total of the two parts.
|
|
/// Part1 and part2 must be actual parts and must be consecutive parts in the split IP_Task.
|
|
/// If the join results in only one part remaining, the all parts will be deleted and the split IP_Task will promote to a regular IP_Task
|
|
/// Schedule of other parts will not be affected.
|
|
/// TODO: Join option: EarlyStartLateEnd, EarlyStartEarlyEnd, LateStartLateEnd
|
|
/// </summary>
|
|
/// <param name="part1">The part to keep in the ProjectManager after the join completes successfully.</param>
|
|
/// <param name="part2">The part to join into part1 and be deleted afterwards from the ProjectManager.</param>
|
|
public void Join(T part1, T part2)
|
|
{
|
|
if (part1 != null
|
|
&& part2 != null
|
|
&& _mSplitIP_TaskOfPart.ContainsKey(part1) // part1 and part2 must already be existing parts
|
|
&& _mSplitIP_TaskOfPart.ContainsKey(part2)
|
|
&& _mSplitIP_TaskOfPart[part1] == _mSplitIP_TaskOfPart[part2] // part1 and part2 must be of the same split IP_Task
|
|
)
|
|
{
|
|
|
|
var split = _mSplitIP_TaskOfPart[part1];
|
|
var parts = _mPartsOfSplitIP_Task[split];
|
|
if (parts.Count > 2)
|
|
{
|
|
// Aggregate part2 into part1, and determine join type
|
|
TimeSpan min; bool join_backwards;
|
|
if (part1.Start < part2.Start) { min = part1.Start; join_backwards = true; }
|
|
else { min = part2.Start; join_backwards = false; }
|
|
TimeSpan duration = part1.Duration + part2.Duration;
|
|
|
|
part1.Start = min;
|
|
part1.Duration = duration;
|
|
part1.End = min + duration;
|
|
|
|
// aggregate resouces
|
|
// TODO: Ask whether to aggregate resources?
|
|
foreach (var r in this.ResourcesOf(part2))
|
|
this.Assign(part1, r);
|
|
this.Unassign(part2);
|
|
|
|
// remove all traces of part2
|
|
parts.Remove(part2);
|
|
_mResourcesOfIP_Task.Remove(part2);
|
|
_mSplitIP_TaskOfPart.Remove(part2);
|
|
_mRegister.Remove(part2);
|
|
|
|
// pack the remaining parts
|
|
if (join_backwards) _PackPartsForward(parts);
|
|
else _PackPartsBackwards(parts);
|
|
|
|
// set the duration
|
|
split.End = parts.Last().End;
|
|
split.Duration = split.End - split.Start;
|
|
split.Start = parts.First().Start;
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
}
|
|
else
|
|
{
|
|
this.Merge(split);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merge all the parts of the splitted IP_Task back into one IP_Task, having duration equal to sum of total duration of individual IP_Task parts, and aggregating the resources onto the resulting IP_Task.
|
|
/// </summary>
|
|
/// <param name="split">The split IP_Task to merge</param>
|
|
public void Merge(T split)
|
|
{
|
|
if (split != null
|
|
&& _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
|
|
)
|
|
{
|
|
TimeSpan duration = TimeSpan.Zero;
|
|
_mPartsOfSplitIP_Task[split].ForEach(x => {
|
|
|
|
// sum durations
|
|
duration += x.Duration;
|
|
|
|
// merge resources onto split IP_Task
|
|
foreach (var r in _mResourcesOfIP_Task[x])
|
|
this.Assign(split, r);
|
|
|
|
// remove traces of all parts
|
|
_mSplitIP_TaskOfPart.Remove(x);
|
|
_mRegister.Remove(x);
|
|
_mResourcesOfIP_Task.Remove(x);
|
|
});
|
|
_mPartsOfSplitIP_Task.Remove(split); // remove split as a split IP_Task
|
|
|
|
// set the duration
|
|
this.SetDuration(split, duration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the parts of the split IP_Task
|
|
/// </summary>
|
|
/// <param name="split"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<T> PartsOf(T split)
|
|
{
|
|
if (split != null
|
|
&& _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
|
|
)
|
|
{
|
|
return _mPartsOfSplitIP_Task[split].Select(x => x);
|
|
}
|
|
else
|
|
{
|
|
return new T[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the split IP_Task that the specified part belogs to.
|
|
/// </summary>
|
|
/// <param name="part"></param>
|
|
/// <returns></returns>
|
|
public T SplitIP_TaskOf(T part)
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(part))
|
|
return _mSplitIP_TaskOfPart[part];
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether the specified IP_Task is a split IP_Task
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public bool IsSplit(T IP_Task)
|
|
{
|
|
return IP_Task != null && _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether the specified IP_Task is a part of a split IP_Task
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
/// <returns></returns>
|
|
public bool IsPart(T IP_Task)
|
|
{
|
|
return IP_Task != null && _mSplitIP_TaskOfPart.ContainsKey(IP_Task);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detach the specified IP_Task from ProjectManager.IP_Tasks (i.e. remove from its parent group, or if not it goes not have a parent group, unregister from root IP_Task status).
|
|
/// The specified IP_Task will remain registered in ProjectManager.
|
|
/// After execution of this helper method, the IP_Task is expected to be re-attached to ProjectManager.IP_Tasks by regaining root IP_Task status, or joining a new group.
|
|
/// </summary>
|
|
/// <param name="IP_Task"></param>
|
|
private void _DetachIP_Task(T IP_Task)
|
|
{
|
|
var group = this.DirectGroupOf(IP_Task);
|
|
if (group == null) // member is actually not in any group, so it must be in _mRootIP_Tasks
|
|
_mRootIP_Tasks.Remove(IP_Task);
|
|
else
|
|
{
|
|
_mMembersOfGroup[group].Remove(IP_Task);
|
|
_mGroupOfMember[IP_Task] = null;
|
|
}
|
|
}
|
|
|
|
private void _SetStartHelper(T IP_Task, TimeSpan value)
|
|
{
|
|
if (IP_Task.Start != value)
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
|
|
{
|
|
// IP_Task part belonging to a split IP_Task needs special treatment
|
|
_SetPartStartHelper(IP_Task, value);
|
|
}
|
|
else // regular IP_Task or a split IP_Task, which we will treat normally
|
|
{
|
|
// check out of bounds
|
|
if (value < TimeSpan.Zero) value = TimeSpan.Zero;
|
|
if (this.DirectPrecedentsOf(IP_Task).Any())
|
|
{
|
|
var max_end = this.DirectPrecedentsOf(IP_Task).Max(x => x.End);
|
|
if (value <= max_end) value = max_end; // + One;
|
|
}
|
|
|
|
// save offset just in case we need to use for moving IP_Task parts
|
|
var offset = value - IP_Task.Start;
|
|
|
|
// cache value
|
|
IP_Task.Duration = IP_Task.End - IP_Task.Start;
|
|
IP_Task.Start = value;
|
|
|
|
// affect self
|
|
IP_Task.End = IP_Task.Start + IP_Task.Duration;
|
|
|
|
// calculate dependants
|
|
_RecalculateDependantsOf(IP_Task);
|
|
|
|
// shift the IP_Task parts accordingly if IP_Task was a split IP_Task
|
|
if (_mPartsOfSplitIP_Task.ContainsKey(IP_Task))
|
|
{
|
|
_mPartsOfSplitIP_Task[IP_Task].ForEach(x => {
|
|
x.Start += offset;
|
|
x.End += offset;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the start date for a group IP_Task. The relative dates between the IP_Tasks in the group will not be affected
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <param name="value"></param>
|
|
private void _SetGroupStartHelper(T group, TimeSpan value)
|
|
{
|
|
if (_mRegister.Contains(group) && value != group.Start && this.IsGroup(group))
|
|
{
|
|
bool earlier = value < group.Start;
|
|
TimeSpan offset = value - group.Start;
|
|
var decendants = earlier ? MembersOf(group).OrderBy((t) => t.Start) : MembersOf(group).OrderByDescending((t) => t.Start);
|
|
|
|
foreach(T decendant in decendants)
|
|
{
|
|
if (this.IsGroup(decendant)) continue;
|
|
|
|
decendant.Start += offset;
|
|
decendant.End += offset;
|
|
|
|
if (this.IsSplit(decendant))
|
|
{
|
|
var parts = _mPartsOfSplitIP_Task[decendant];
|
|
foreach (T part in parts)
|
|
{
|
|
part.Start += offset;
|
|
part.End += offset;
|
|
}
|
|
}
|
|
|
|
_RecalculateDependantsOf(decendant);
|
|
}
|
|
|
|
_RecalculateAncestorsSchedule();
|
|
_RecalculateSlack();
|
|
}
|
|
}
|
|
|
|
private void _SetEndHelper(T IP_Task, TimeSpan value)
|
|
{
|
|
if (IP_Task.End != value)
|
|
{
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
|
|
{
|
|
// IP_Task part belonging to a split IP_Task needs special treatment
|
|
_SetPartEndHelper(IP_Task, value);
|
|
}
|
|
else // regular IP_Task or a split IP_Task, which we will treat normally
|
|
{
|
|
// check bounds
|
|
bool isSplitIP_Task = _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
|
|
T last_part = null;
|
|
if (isSplitIP_Task)
|
|
{
|
|
last_part = _mPartsOfSplitIP_Task[IP_Task].Last();
|
|
if (value <= last_part.Start) value = last_part.Start + TimeSpan.FromMinutes(30);
|
|
}
|
|
if (value <= IP_Task.Start) value = IP_Task.Start + TimeSpan.FromMinutes(30); // end cannot be less than start
|
|
|
|
// assign end value
|
|
IP_Task.End = value;
|
|
IP_Task.Duration = IP_Task.End - IP_Task.Start;
|
|
|
|
_RecalculateDependantsOf(IP_Task);
|
|
|
|
if (isSplitIP_Task)
|
|
{
|
|
last_part.End = value;
|
|
last_part.Duration = last_part.End - last_part.Start;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _SetPartStartHelper(T part, TimeSpan value)
|
|
{
|
|
var split = _mSplitIP_TaskOfPart[part];
|
|
var parts = _mPartsOfSplitIP_Task[split];
|
|
|
|
// check bounds
|
|
if (this.DirectPrecedentsOf(split).Any())
|
|
{
|
|
var max_end = this.DirectPrecedentsOf(split).Max(x => x.End);
|
|
if (value < max_end) value = max_end;
|
|
}
|
|
if (value < TimeSpan.Zero) value = TimeSpan.Zero;
|
|
|
|
// flag whether we need to pack parts forward or backwards
|
|
bool backwards = value < part.Start;
|
|
|
|
// assign start value, maintining duration and modifying end
|
|
var duration = part.End - part.Start;
|
|
part.Start = value;
|
|
part.End = value + duration;
|
|
|
|
// pack packs
|
|
if (backwards) _PackPartsBackwards(parts);
|
|
else _PackPartsForward(parts);
|
|
|
|
// recalculate the split
|
|
split.Start = parts.First().Start; // recalculate the split IP_Task
|
|
split.End = parts.Last().End;
|
|
split.Duration = split.End - split.Start;
|
|
|
|
_RecalculateDependantsOf(split);
|
|
}
|
|
|
|
private void _SetPartEndHelper(T part, TimeSpan value)
|
|
{
|
|
var split = _mSplitIP_TaskOfPart[part];
|
|
var parts = _mPartsOfSplitIP_Task[split];
|
|
|
|
// check for bounds
|
|
if (value <= part.Start) value = part.Start + TimeSpan.FromMinutes(30);
|
|
|
|
// flag whether duration is increased or reduced
|
|
bool increased = value > part.End;
|
|
|
|
// set end value and duration
|
|
part.End = value;
|
|
part.Duration = part.End - part.Start;
|
|
|
|
// pack parts
|
|
if (increased) _PackPartsForward(parts);
|
|
|
|
// recalculate the split
|
|
split.Start = parts.First().Start; // recalculate the split IP_Task
|
|
split.End = parts.Last().End;
|
|
split.Duration = split.End - split.Start;
|
|
|
|
_RecalculateDependantsOf(split);
|
|
}
|
|
|
|
private void _PackPartsBackwards(List<T> parts)
|
|
{
|
|
// pack backwards first before packing forward again
|
|
for (int i = parts.Count - 2; i > 0; i--) // Cannot pack beyond first part (i > 0)
|
|
{
|
|
var earlier = parts[i];
|
|
var later = parts[i + 1];
|
|
if (later.Start <= earlier.End)
|
|
{
|
|
earlier.End = later.Start;
|
|
earlier.Start = earlier.End - earlier.Duration;
|
|
}
|
|
}
|
|
|
|
_PackPartsForward(parts);
|
|
}
|
|
|
|
private void _PackPartsForward(List<T> parts)
|
|
{
|
|
for (int i = 1; i < parts.Count; i++)
|
|
{
|
|
var current = parts[i];
|
|
var previous = parts[i - 1];
|
|
if (previous.End >= current.Start)
|
|
{
|
|
current.Start = previous.End;
|
|
current.End = current.Start + current.Duration;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _SetCompleteHelper(T IP_Task, float value)
|
|
{
|
|
if (IP_Task.Complete != value)
|
|
{
|
|
if (value > 1) value = 1;
|
|
else if (value < 0) value = 0;
|
|
IP_Task.Complete = value;
|
|
|
|
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
|
|
{
|
|
var split = _mSplitIP_TaskOfPart[IP_Task];
|
|
var parts = _mPartsOfSplitIP_Task[split];
|
|
float complete = 0;
|
|
TimeSpan duration = TimeSpan.Zero;
|
|
foreach (var part in parts)
|
|
{
|
|
complete += part.Complete * part.Duration.Ticks;
|
|
duration += part.Duration;
|
|
}
|
|
split.Complete = complete / duration.Ticks;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _RecalculateComplete()
|
|
{
|
|
Stack<T> groups = new Stack<T>();
|
|
foreach (var IP_Task in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
|
|
{
|
|
_RecalculateCompletedHelper(IP_Task);
|
|
}
|
|
}
|
|
|
|
private float _RecalculateCompletedHelper(T groupOrSplit)
|
|
{
|
|
float t_complete = 0;
|
|
TimeSpan t_duration = TimeSpan.Zero;
|
|
|
|
if (_mPartsOfSplitIP_Task.ContainsKey(groupOrSplit))
|
|
{
|
|
foreach (var part in _mPartsOfSplitIP_Task[groupOrSplit])
|
|
{
|
|
t_complete += part.Complete * part.Duration.Ticks;
|
|
t_duration += part.Duration;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var member in this.DirectMembersOf(groupOrSplit))
|
|
{
|
|
t_duration += member.Duration;
|
|
if (this.IsGroup(member)) t_complete += _RecalculateCompletedHelper(member) * member.Duration.Ticks;
|
|
else t_complete += member.Complete * member.Duration.Ticks;
|
|
}
|
|
}
|
|
|
|
groupOrSplit.Complete = t_complete / t_duration.Ticks;
|
|
|
|
|
|
return groupOrSplit.Complete;
|
|
}
|
|
|
|
private void _RecalculateDependantsOf(T precedent)
|
|
{
|
|
// affect decendants
|
|
foreach (var dependant in this.DirectDependantsOf(precedent))
|
|
{
|
|
if (dependant.Start < precedent.End)
|
|
this._SetStartHelper(dependant, precedent.End);
|
|
}
|
|
}
|
|
|
|
private void _RecalculateAncestorsSchedule()
|
|
{
|
|
// affects parent group
|
|
foreach(var group in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
|
|
{
|
|
_RecalculateAncestorsScheduleHelper(group);
|
|
}
|
|
}
|
|
|
|
private void _RecalculateAncestorsScheduleHelper(T group)
|
|
{
|
|
float t_complete = 0;
|
|
TimeSpan t_duration = TimeSpan.Zero;
|
|
var start = TimeSpan.MaxValue;
|
|
var end = TimeSpan.MinValue;
|
|
foreach (var member in this.DirectMembersOf(group))
|
|
{
|
|
if (this.IsGroup(member))
|
|
_RecalculateAncestorsScheduleHelper(member);
|
|
|
|
t_duration += member.Duration;
|
|
t_complete += member.Complete * member.Duration.Ticks;
|
|
if (member.Start < start) start = member.Start;
|
|
if (member.End > end) end = member.End;
|
|
}
|
|
|
|
this._SetStartHelper(group, start);
|
|
this._SetEndHelper(group, end);
|
|
this._SetCompleteHelper(group, t_complete / t_duration.Ticks);
|
|
}
|
|
|
|
private void _RecalculateSlack()
|
|
{
|
|
var max_end = this.IP_Tasks.Max(x => x.End);
|
|
foreach (var IP_Task in this.IP_Tasks)
|
|
{
|
|
// affects slack for current IP_Task
|
|
if (this.DirectDependantsOf(IP_Task).Any())
|
|
{
|
|
// slack until the earliest dependant needs to start
|
|
var min = this.DirectDependantsOf(IP_Task).Min(x => x.Start);
|
|
IP_Task.Slack = min - IP_Task.End;
|
|
}
|
|
else
|
|
{
|
|
// no dependants, so we have all the time until the last IP_Task ends
|
|
IP_Task.Slack = max_end - IP_Task.End;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|