22#include "kptappointment.h"
23#include "kptaccount.h"
24#include "kptwbsdefinition.h"
25#include "kptresource.h"
26#include "kptschedule.h"
37Node::Node(Node *parent) : m_nodes(), m_dependChildNodes(), m_dependParentNodes() {
44Node::Node(Node &node, Node *parent)
47 m_dependParentNodes() {
52 m_leader = node.leader();
53 m_description = node.description();
54 m_constraint = (ConstraintType) node.constraint();
55 m_constraintStartTime = node.constraintStartTime();
56 m_constraintEndTime = node.constraintEndTime();
58 m_dateOnlyStartDate = node.startDate();
59 m_dateOnlyEndDate = node.endDate();
61 m_runningAccount = node.runningAccount();
62 m_startupAccount = node.startupAccount();
63 m_shutdownAccount = node.shutdownAccount();
65 m_startupCost = node.startupCost();
66 m_shutdownCost = node.shutdownCost();
68 m_schedules.setAutoDelete(node.m_schedules.autoDelete());
72 if (findNode() ==
this) {
76 while ((rel = m_dependParentNodes.getFirst())) {
79 while ((rel = m_dependChildNodes.getFirst())) {
83 m_runningAccount->removeRunning(*
this);
85 m_startupAccount->removeStartup(*
this);
86 if (m_shutdownAccount)
87 m_shutdownAccount->removeShutdown(*
this);
91 m_currentSchedule = 0;
92 m_nodes.setAutoDelete(
true);
94 m_constraint = Node::ASAP;
96 m_visitedForward =
false;
97 m_visitedBackward =
false;
99 m_dateOnlyStartDate = m_dateOnlyEndDate = TQDate::currentDate();
100 m_dateOnlyDuration.addDays(1);
102 m_runningAccount = 0;
103 m_startupAccount = 0;
104 m_shutdownAccount = 0;
106 m_shutdownCost = 0.0;
110 if ((type() == Type_Project) || (type() == Type_Subproject)) {
116 kdError()<<k_funcinfo<<
"Ooops, no parent and no project found"<<endl;
120void Node::delChildNode(
Node *node,
bool remove) {
122 if ( m_nodes.findRef(node) != -1 ) {
131void Node::delChildNode(
int number,
bool remove) {
132 Node *n = m_nodes.at(number);
135 m_nodes.remove(number);
137 m_nodes.take(number);
144void Node::insertChildNode(
unsigned int index, Node *node) {
146 m_nodes.insert(index,node);
147 node->setParent(
this);
150void Node::addChildNode( Node *node, Node *after) {
152 int index = m_nodes.findRef(after);
155 m_nodes.append(node);
156 node->setParent(
this);
160 m_nodes.insert(index+1, node);
161 node->setParent(
this);
164int Node::findChildNode( Node* node )
166 return m_nodes.findRef( node );
170const Node* Node::getChildNode(
int number)
const {
172 const TQPtrList<Node> &nodes = m_nodes;
173 return (
const_cast<TQPtrList<Node> &
>(nodes)).at(number);
183void Node::addDependChildNode(
Node *node, Relation::Type p) {
184 addDependChildNode(node,p,
Duration());
187void Node::addDependChildNode(
Node *node, Relation::Type p,
Duration lag) {
190 m_dependChildNodes.append(relation);
195void Node::insertDependChildNode(
unsigned int index,
Node *node, Relation::Type p) {
198 m_dependChildNodes.insert(index, relation);
203bool Node::addDependChildNode(
Relation *relation) {
204 if(m_dependChildNodes.findRef(relation) != -1)
206 m_dependChildNodes.append(relation);
211void Node::delDependChildNode(
Node *node,
bool remove) {
212 if ( m_nodes.findRef(node) != -1 ) {
214 m_dependChildNodes.remove();
216 m_dependChildNodes.take();
220void Node::delDependChildNode( Relation *rel,
bool remove) {
221 if ( m_dependChildNodes.findRef(rel) != -1 ) {
223 m_dependChildNodes.remove();
225 m_dependChildNodes.take();
229void Node::delDependChildNode(
int number,
bool remove) {
231 m_dependChildNodes.remove(number);
233 m_dependChildNodes.take(number);
237 if (m_dependChildNodes.findRef(rel) != -1) {
238 m_dependChildNodes.take();
242void Node::addDependParentNode(
Node *node, Relation::Type p) {
243 addDependParentNode(node,p,
Duration());
246void Node::addDependParentNode(
Node *node, Relation::Type p,
Duration lag) {
249 m_dependParentNodes.append(relation);
254void Node::insertDependParentNode(
unsigned int index,
Node *node, Relation::Type p) {
257 m_dependParentNodes.insert(index,relation);
262bool Node::addDependParentNode(
Relation *relation) {
263 if(m_dependParentNodes.findRef(relation) != -1)
265 m_dependParentNodes.append(relation);
270void Node::delDependParentNode(
Node *node,
bool remove) {
271 if ( m_nodes.findRef(node) != -1 ) {
273 m_dependParentNodes.remove();
275 m_dependParentNodes.take();
279void Node::delDependParentNode( Relation *rel,
bool remove) {
280 if ( m_dependParentNodes.findRef(rel) != -1 ) {
282 m_dependParentNodes.remove();
284 m_dependParentNodes.take();
288void Node::delDependParentNode(
int number,
bool remove) {
290 m_dependParentNodes.remove(number);
292 m_dependParentNodes.take(number);
296 if (m_dependParentNodes.findRef(rel) != -1) {
297 rel = m_dependParentNodes.take();
301bool Node::isParentOf(
Node *node) {
302 if (m_nodes.findRef(node) != -1)
305 TQPtrListIterator<Node> nit(childNodeIterator());
306 for ( ; nit.current(); ++nit ) {
307 if (nit.current()->isParentOf(node))
313Relation *Node::findParentRelation(Node *node) {
314 for (
int i=0; i<numDependParentNodes(); i++) {
315 Relation *rel = getDependParentNode(i);
316 if (rel->parent() == node)
319 return (Relation *)0;
322Relation *Node::findChildRelation(Node *node) {
323 for (
int i=0; i<numDependChildNodes(); i++) {
324 Relation *rel = getDependChildNode(i);
325 if (rel->child() == node)
328 return (Relation *)0;
331Relation *Node::findRelation(Node *node) {
332 Relation *rel = findParentRelation(node);
334 rel = findChildRelation(node);
338bool Node::isDependChildOf(Node *node) {
340 for (
int i=0; i<numDependParentNodes(); i++) {
341 Relation *rel = getDependParentNode(i);
342 if (rel->parent() == node)
344 if (rel->parent()->isDependChildOf(node))
353 if (!time.isValid()) {
354 kdError()<<k_funcinfo<<
"Time is invalid"<<endl;
355 return Duration::zeroDuration;
358 kdError()<<k_funcinfo<<
"m_effort == 0"<<endl;
359 return Duration::zeroDuration;
361 if (m_currentSchedule == 0) {
362 return Duration::zeroDuration;
363 kdError()<<k_funcinfo<<
"No current schedule"<<endl;
365 kdDebug()<<k_funcinfo<<m_name<<
": Use="<<use<<endl;
366 return calcDuration(time, m_effort->effort(use), backward);
369void Node::makeAppointments() {
370 TQPtrListIterator<Node> nit(m_nodes);
371 for ( ; nit.current(); ++nit ) {
372 nit.current()->makeAppointments();
376void Node::calcResourceOverbooked() {
377 TQPtrListIterator<Node> nit(m_nodes);
378 for ( ; nit.current(); ++nit ) {
379 nit.current()->calcResourceOverbooked();
383TQStringList Node::overbookedResources()
const {
384 return m_currentSchedule ? m_currentSchedule->overbookedResources() : TQStringList();
387void Node::saveRelations(TQDomElement &element)
const {
388 TQPtrListIterator<Relation> it(m_dependChildNodes);
389 for (; it.current(); ++it) {
390 it.current()->save(element);
392 TQPtrListIterator<Node> nodes(m_nodes);
393 for ( ; nodes.current(); ++nodes ) {
394 nodes.current()->saveRelations(element);
398void Node::setConstraint(TQString &type) {
402 else if (type ==
"ALAP")
404 else if (type ==
"StartNotEarlier")
405 setConstraint(StartNotEarlier);
406 else if (type ==
"FinishNotLater")
407 setConstraint(FinishNotLater);
408 else if (type ==
"MustStartOn")
409 setConstraint(MustStartOn);
410 else if (type ==
"MustFinishOn")
411 setConstraint(MustFinishOn);
412 else if (type ==
"FixedInterval")
413 setConstraint(FixedInterval);
418TQString Node::constraintToString()
const {
420 if (m_constraint == ASAP)
421 return TQString(
"ASAP");
422 else if (m_constraint == ALAP)
423 return TQString(
"ALAP");
424 else if (m_constraint == StartNotEarlier)
425 return TQString(
"StartNotEarlier");
426 else if (m_constraint == FinishNotLater)
427 return TQString(
"FinishNotLater");
428 else if (m_constraint == MustStartOn)
429 return TQString(
"MustStartOn");
430 else if (m_constraint == MustFinishOn)
431 return TQString(
"MustFinishOn");
432 else if (m_constraint == FixedInterval)
433 return TQString(
"FixedInterval");
438void Node::propagateEarliestStart(DateTime &time) {
439 if (m_currentSchedule == 0)
441 m_currentSchedule->earliestStart = time;
443 TQPtrListIterator<Node> it = m_nodes;
444 for (; it.current(); ++it) {
445 it.current()->propagateEarliestStart(time);
449void Node::propagateLatestFinish(DateTime &time) {
450 if (m_currentSchedule == 0)
452 m_currentSchedule->latestFinish = time;
454 TQPtrListIterator<Node> it = m_nodes;
455 for (; it.current(); ++it) {
456 it.current()->propagateLatestFinish(time);
460void Node::moveEarliestStart(DateTime &time) {
461 if (m_currentSchedule == 0)
463 if (m_currentSchedule->earliestStart < time)
464 m_currentSchedule->earliestStart = time;
465 TQPtrListIterator<Node> it = m_nodes;
466 for (; it.current(); ++it) {
467 it.current()->moveEarliestStart(time);
471void Node::moveLatestFinish(DateTime &time) {
472 if (m_currentSchedule == 0)
474 if (m_currentSchedule->latestFinish > time)
475 m_currentSchedule->latestFinish = time;
476 TQPtrListIterator<Node> it = m_nodes;
477 for (; it.current(); ++it) {
478 it.current()->moveLatestFinish(time);
482void Node::initiateCalculation(Schedule &sch) {
483 TQPtrListIterator<Node> it = m_nodes;
484 for (; it.current(); ++it) {
485 it.current()->initiateCalculation(sch);
489void Node::resetVisited() {
490 m_visitedForward =
false;
491 m_visitedBackward =
false;
492 TQPtrListIterator<Node> it = m_nodes;
493 for (; it.current(); ++it) {
494 it.current()->resetVisited();
498Node *Node::siblingBefore() {
501 return getParent()->childBefore(
this);
505Node *Node::childBefore(Node *node) {
507 int index = m_nodes.findRef(node);
509 return m_nodes.at(index-1);
514Node *Node::siblingAfter() {
517 return getParent()->childAfter(
this);
521Node *Node::childAfter(Node *node)
524 uint index = m_nodes.findRef(node);
525 if (index < m_nodes.count()-1) {
526 return m_nodes.at(index+1); }
530bool Node::moveChildUp(Node* node)
532 if (findChildNode(node) == -1)
534 Node *sib = node->siblingBefore();
537 sib = sib->siblingBefore();
538 delChildNode(node,
false);
540 addChildNode(node, sib);
542 insertChildNode(0, node);
547bool Node::moveChildDown(Node* node)
549 if (findChildNode(node) == -1)
551 Node *sib = node->siblingAfter();
554 delChildNode(node,
false);
555 addChildNode(node, sib);
559bool Node::legalToLink(
Node *node) {
560 Node *p = projectNode();
566bool Node::isEndNode()
const {
567 return m_dependChildNodes.isEmpty();
569bool Node::isStartNode()
const {
570 return m_dependParentNodes.isEmpty();
573bool Node::setId(TQString
id) {
576 kdError()<<k_funcinfo<<
"id is empty"<<endl;
580 if (!m_id.isEmpty()) {
581 Node *n = findNode();
587 kdError()<<k_funcinfo<<
"My id '"<<m_id<<
"' already used for different node: "<<n->name()<<endl;
591 kdError()<<k_funcinfo<<
"id '"<<
id<<
"' is already used for different node: "<<findNode(
id)->name()<<endl;
601void Node::setStartTime(DateTime startTime) {
602 if (m_currentSchedule)
603 m_currentSchedule->startTime = startTime;
604 m_dateOnlyStartDate = startTime.date();
607void Node::setEndTime(DateTime endTime) {
608 if (m_currentSchedule)
609 m_currentSchedule->endTime = endTime;
611 m_dateOnlyEndDate = endTime.date();
612 if (endTime.time().isNull() && m_dateOnlyEndDate > m_dateOnlyStartDate)
613 m_dateOnlyEndDate = m_dateOnlyEndDate.addDays(-1);
616void Node::saveAppointments(TQDomElement &element,
long id)
const {
618 TQPtrListIterator<Node> it(m_nodes);
619 for (; it.current(); ++it ) {
620 it.current()->saveAppointments(element,
id);
624TQPtrList<Appointment> Node::appointments() {
625 TQPtrList<Appointment> lst;
626 if (m_currentSchedule)
627 lst = m_currentSchedule->appointments();
637 if (m_currentSchedule)
638 return m_currentSchedule->add(appointment);
644 Schedule *s = findSchedule(main.id());
646 s = createSchedule(&main);
648 appointment->setNode(s);
649 return s->
add(appointment);
653 Schedule *node = findSchedule(resource->id());
655 node = createSchedule(resource->parent());
663 if (m_currentSchedule == schedule)
664 m_currentSchedule = 0;
665 m_schedules.take(schedule->id());
671 m_schedules.replace(schedule->id(), schedule);
674Schedule *Node::createSchedule(TQString name, Schedule::Type type,
long id) {
688Schedule *Node::findSchedule(
const TQString name,
const Schedule::Type type)
const {
689 TQIntDictIterator<Schedule> it = m_schedules;
690 for (; it.current(); ++it) {
691 if (!it.current()->isDeleted() &&
692 it.current()->name() == name && it.current()->type() == type)
698Schedule *Node::findSchedule(
const Schedule::Type type)
const {
700 TQIntDictIterator<Schedule> it = m_schedules;
701 for (; it.current(); ++it) {
702 if (!it.current()->isDeleted() && it.current()->type() == type) {
709void Node::setScheduleDeleted(
long id,
bool on) {
712 kdError()<<k_funcinfo<<m_name<<
" Could not find schedule with id="<<
id<<endl;
719 Schedule *s = findSchedule(sch->id());
723 TQPtrListIterator<Node> it = m_nodes;
724 for (; it.current(); ++it) {
725 it.current()->setParentSchedule(sch);
729bool Node::calcCriticalPath(
bool fromEnd) {
730 if (m_currentSchedule == 0)
736 if (!fromEnd && isStartNode()) {
737 m_currentSchedule->inCriticalPath =
true;
740 if (fromEnd && isEndNode()) {
741 m_currentSchedule->inCriticalPath =
true;
744 TQPtrListIterator<Relation> pit(m_dependParentNodes);
745 for (; pit.current(); ++pit) {
746 if (pit.current()->parent()->calcCriticalPath(fromEnd)) {
747 m_currentSchedule->inCriticalPath =
true;
750 return m_currentSchedule->inCriticalPath;
754 Node *n = getParent();
755 return n ? n->
level() + 1 : 0;
758void Node::generateWBS(
int count, WBSDefinition &def, TQString wbs) {
759 m_wbs = wbs + def.code(count, level());
761 TQString w = wbs + def.wbs(count, level());
762 TQPtrListIterator<Node> it = m_nodes;
763 for (
int i=0; it.current(); ++it) {
764 it.current()->generateWBS(++i, def, w);
769void Node::setCurrentSchedule(
long id) {
770 TQPtrListIterator<Node> it = m_nodes;
771 for (; it.current(); ++it) {
772 it.current()->setCurrentSchedule(
id);
779 m_expectedEffort = e;
780 m_pessimisticEffort = p;
781 m_optimisticEffort = o;
782 m_type = Type_Effort;
783 m_risktype = Risk_None;
786Effort::Effort(
const Effort &effort) {
787 set(effort.expected(), effort.pessimistic(), effort.optimistic());
788 setType(effort.type());
789 setRisktype(effort.risktype());
795const Effort Effort::zeroEffort( Duration::zeroDuration,
796 Duration::zeroDuration,
797 Duration::zeroDuration );
799void Effort::set( Duration e, Duration p, Duration o ) {
800 m_expectedEffort = e;
801 m_pessimisticEffort = (p == Duration::zeroDuration) ? e : p;
802 m_optimisticEffort = (o == Duration::zeroDuration) ? e : o;
806void Effort::set(
int e,
int p,
int o ) {
807 m_expectedEffort = Duration(e);
808 m_pessimisticEffort = (p < 0) ? Duration(e) : Duration(p);
809 m_optimisticEffort = (o < 0) ? Duration(e) : Duration(o);
818void Effort::set(
unsigned days,
unsigned hours,
unsigned minutes) {
819 Duration dur(days, hours, minutes);
824void Effort::expectedEffort(
unsigned *days,
unsigned *hours,
unsigned *minutes) {
825 m_expectedEffort.get(days, hours, minutes);
828Duration Effort::variance()
const {
829 return (m_pessimisticEffort - m_optimisticEffort)/6;
831Duration Effort::pertExpected()
const {
832 if (m_risktype == Risk_Low) {
833 return (m_optimisticEffort + m_pessimisticEffort + (m_expectedEffort*4))/6;
834 }
else if (m_risktype == Risk_High) {
835 return (m_optimisticEffort + (m_pessimisticEffort*2) + (m_expectedEffort*4))/7;
837 return m_expectedEffort;
839Duration Effort::pertOptimistic()
const {
840 if (m_risktype != Risk_None) {
841 return pertExpected() - variance();
843 return m_optimisticEffort;
845Duration Effort::pertPessimistic()
const {
846 if (m_risktype != Risk_None) {
847 return pertExpected() + variance();
849 return m_pessimisticEffort;
852Duration Effort::effort(
int use)
const {
853 if (use == Effort::Use_Expected) {
854 return pertExpected();
855 }
else if (use == Effort::Use_Optimistic) {
856 return pertOptimistic();
857 }
else if (use == Effort::Use_Pessimistic)
858 return pertPessimistic();
860 return m_expectedEffort;
863bool Effort::load(TQDomElement &element) {
864 m_expectedEffort = Duration::fromString(element.attribute(
"expected"));
865 m_optimisticEffort = Duration::fromString(element.attribute(
"optimistic"));
866 m_pessimisticEffort = Duration::fromString(element.attribute(
"pessimistic"));
867 setType(element.attribute(
"type",
"WorkBased"));
868 setRisktype(element.attribute(
"risk"));
872void Effort::save(TQDomElement &element)
const {
873 TQDomElement me = element.ownerDocument().createElement(
"effort");
874 element.appendChild(me);
875 me.setAttribute(
"expected", m_expectedEffort.toString());
876 me.setAttribute(
"optimistic", m_optimisticEffort.toString());
877 me.setAttribute(
"pessimistic", m_pessimisticEffort.toString());
878 me.setAttribute(
"type", typeToString());
879 me.setAttribute(
"risk", risktypeToString());
882TQString Effort::typeToString()
const {
883 if (m_type == Type_Effort)
884 return TQString(
"Effort");
885 if (m_type == Type_FixedDuration)
886 return TQString(
"Type_FixedDuration");
891void Effort::setType(TQString type) {
892 if (type ==
"Effort")
893 setType(Type_Effort);
894 else if (type ==
"Type_FixedDuration")
895 setType(Type_FixedDuration);
897 setType(Type_Effort);
900TQString Effort::risktypeToString()
const {
901 if (m_risktype == Risk_None)
902 return TQString(
"None");
903 if (m_risktype == Risk_Low)
904 return TQString(
"Low");
905 if (m_risktype == Risk_High)
906 return TQString(
"High");
911void Effort::setRisktype(TQString type) {
913 setRisktype(Risk_High);
914 else if (type ==
"Low")
915 setRisktype(Risk_Low);
917 setRisktype(Risk_None);
920void Effort::setOptimisticRatio(
int percent)
922 int p = percent>0 ? -percent : percent;
923 m_optimisticEffort = m_expectedEffort*(100+p)/100;
926int Effort::optimisticRatio()
const {
927 if (m_expectedEffort == Duration::zeroDuration)
929 return (m_optimisticEffort.milliseconds()*100/m_expectedEffort.milliseconds())-100;
932void Effort::setPessimisticRatio(
int percent)
934 int p = percent<0 ? -percent : percent;
935 m_pessimisticEffort = m_expectedEffort*(100+p)/100;
937int Effort::pessimisticRatio()
const {
938 if (m_expectedEffort == Duration::zeroDuration)
940 return m_pessimisticEffort.milliseconds()*100/m_expectedEffort.milliseconds()-100;
945void Node::printDebug(
bool children, TQCString indent) {
946 kdDebug()<<indent<<
" Unique node identity="<<m_id<<endl;
947 if (m_effort) m_effort->printDebug(indent);
948 TQString s =
" Constraint: " + constraintToString();
949 if (m_constraint == MustStartOn || m_constraint == StartNotEarlier || m_constraint == FixedInterval)
950 kdDebug()<<indent<<s<<
" ("<<constraintStartTime().toString()<<
")"<<endl;
951 if (m_constraint == MustFinishOn || m_constraint == FinishNotLater || m_constraint == FixedInterval)
952 kdDebug()<<indent<<s<<
" ("<<constraintEndTime().toString()<<
")"<<endl;
955 kdDebug()<<indent<<
" Current schedule: "<<
"id="<<cs->id()<<
" '"<<cs->name()<<
"' type: "<<cs->type()<<endl;
957 kdDebug()<<indent<<
" Current schedule: None"<<endl;
959 TQIntDictIterator<Schedule> it = m_schedules;
960 for (; it.current(); ++it) {
961 it.current()->printDebug(indent+
" ");
963 kdDebug()<<indent<<
" Parent: "<<(m_parent ? m_parent->name() : TQString(
"None"))<<endl;
964 kdDebug()<<indent<<
" Level: "<<level()<<endl;
965 kdDebug()<<indent<<
" No of predecessors: "<<m_dependParentNodes.count()<<endl;
966 TQPtrListIterator<Relation> pit(m_dependParentNodes);
968 if (pit.count() > 0) {
969 for ( ; pit.current(); ++pit ) {
970 pit.current()->printDebug(indent);
973 kdDebug()<<indent<<
" No of successors: "<<m_dependChildNodes.count()<<endl;
974 TQPtrListIterator<Relation> cit(m_dependChildNodes);
976 if (cit.count() > 0) {
977 for ( ; cit.current(); ++cit ) {
978 cit.current()->printDebug(indent);
985 TQPtrListIterator<Node> it(m_nodes);
986 for ( ; it.current(); ++it ) {
987 it.current()->printDebug(
true,indent);
996void Effort::printDebug(TQCString indent) {
997 kdDebug()<<indent<<
" Effort:"<<endl;
999 kdDebug()<<indent<<
" Expected: "<<m_expectedEffort.toString()<<endl;
1000 kdDebug()<<indent<<
" Optimistic: "<<m_optimisticEffort.toString()<<endl;
1001 kdDebug()<<indent<<
" Pessimistic: "<<m_pessimisticEffort.toString()<<endl;
1003 kdDebug()<<indent<<
" Risk: "<<risktypeToString()<<endl;
1004 kdDebug()<<indent<<
" Pert expected: "<<pertExpected().toString()<<endl;
1005 kdDebug()<<indent<<
" Pert optimistic: "<<pertOptimistic().toString()<<endl;
1006 kdDebug()<<indent<<
" Pert pessimistic: "<<pertPessimistic().toString()<<endl;
1007 kdDebug()<<indent<<
" Pert variance: "<<variance().toString()<<endl;
A resource (Resource) can be scheduled to be used at any time, this is represented internally with Ap...
DateTime is a TQDateTime which knows about Duration.
The duration class can be used to store a timespan in a convenient format.
NodeSchedule holds scheduling information for a node (task).
This class represents any node in the project, a node can be a project or a subproject or any task.
virtual void addDependChildNode(Node *node, Relation::Type p=Relation::FinishStart)
Adds relation to both this node and address node.
virtual int level()
Returns the level this node is in the hierarchy. Top node is level 0.
bool legalToLink(Node *node)
Check if this node can be linked to node.
virtual Node * projectNode()
Returns a pointer to the project node (main- or sub-project) Returns 0 if no project exists.
virtual void addDependParentNode(Node *node, Relation::Type p=Relation::FinishStart)
Adds relation to both this node and node.
The relation class couples a 2 nodes together which are dependent on each other.
ResourceSchedule holds scheduling information for a resource.
The Schedule class holds data calculated during project calculation and scheduling,...
virtual bool add(Appointment *appointment)
Adds appointment to this schedule only.
virtual void addAppointment(Schedule *, DateTime &, DateTime &, double=100)
Adds appointment to both this resource schedule and node schedule.