diff --git a/src/libs/kernel/kptnode.cpp b/src/libs/kernel/kptnode.cpp index d5e22681..3b0d533a 100644 --- a/src/libs/kernel/kptnode.cpp +++ b/src/libs/kernel/kptnode.cpp @@ -1,1884 +1,1891 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2002 - 2010, 2012 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptnode.h" #include "kptappointment.h" #include "kptaccount.h" #include "kptwbsdefinition.h" #include "kptresource.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" +#include "XmlSaveContext.h" #include "kptdebug.h" #include #include #include namespace KPlato { Node::Node(Node *parent) : QObject( 0 ), // We don't use qobjects parent m_nodes(), m_dependChildNodes(), m_dependParentNodes(), m_estimate( 0 ), m_blockChanged(false) { //debugPlan<<"("<removeRunning(*this); if (m_startupAccount) m_startupAccount->removeStartup(*this); if (m_shutdownAccount) m_shutdownAccount->removeShutdown(*this); foreach (Schedule *s, m_schedules) { delete s; } m_schedules.clear(); m_parent = 0; //safety } void Node::init() { m_documents.node = this; m_currentSchedule = 0; m_name=""; m_constraint = Node::ASAP; m_estimate = 0; m_visitedForward = false; m_visitedBackward = false; m_runningAccount = 0; m_startupAccount = 0; m_shutdownAccount = 0; m_startupCost = 0.0; m_shutdownCost = 0.0; } QString Node::typeToString( bool trans ) const { return typeToString( (Node::NodeTypes)type(), trans ); } // static QString Node::typeToString( Node::NodeTypes typ, bool trans ) { return typeToStringList( trans ).at( typ ); } // static QStringList Node::typeToStringList( bool trans ) { return QStringList() << ( trans ? i18n( "None" ) : QString( "None" ) ) << ( trans ? i18n( "Project" ) : QString( "Project" ) ) << ( trans ? i18n( "Sub-Project" ) : QString( "Sub-Project" ) ) << ( trans ? i18n( "Task" ) : QString( "Task" ) ) << ( trans ? i18n( "Milestone" ) : QString( "Milestone" ) ) << ( trans ? i18n( "Periodic" ) : QString( "Periodic" ) ) << ( trans ? i18n( "Summary" ) : QString( "Summary-Task" ) ); } void Node::setName(const QString &n) { #ifndef NDEBUG setObjectName( n ); #endif m_name = n; changed(this); } void Node::setLeader(const QString &l) { m_leader = l; changed(this); } void Node::setDescription(const QString &d) { m_description = d; changed(this); } Node *Node::projectNode() { if ((type() == Type_Project) || (type() == Type_Subproject)) { return this; } if (m_parent) return m_parent->projectNode(); // This happens for default tasks return 0; } const Node *Node::projectNode() const { if ((type() == Type_Project) || (type() == Type_Subproject)) { return this; } if (m_parent) return m_parent->projectNode(); // This happens for default tasks return 0; } void Node::takeChildNode( Node *node) { //debugPlan<<"find="<setParentNode(0); if ( t != type() ) { changed( Type ); } } void Node::takeChildNode( int number ) { int t = type(); if (number >= 0 && number < m_nodes.size()) { Node *n = m_nodes.takeAt(number); //debugPlan<<(n?n->id():"null")<<" :"<<(n?n->name():""); if (n) { n->setParentNode( 0 ); } } if ( t != type() ) { changed( Type ); } } void Node::insertChildNode( int index, Node *node ) { int t = type(); if (index == -1) m_nodes.append(node); else m_nodes.insert(index,node); node->setParentNode( this ); if ( t != type() ) { changed( Type ); } } void Node::addChildNode( Node *node, Node *after) { int t = type(); int index = m_nodes.indexOf(after); if (index == -1) { m_nodes.append(node); node->setParentNode( this ); if ( t != type() ) { changed( Type ); } return; } m_nodes.insert(index+1, node); node->setParentNode(this); if ( t != type() ) { changed( Type ); } } int Node::findChildNode( const Node* node ) const { return m_nodes.indexOf( const_cast( node ) ); } bool Node::isChildOf( const Node* node ) const { if ( node == 0 || m_parent == 0 ) { return false; } if ( node == m_parent ) { return true; } return m_parent->isChildOf( node ); } Node* Node::childNode(int number) { //debugPlan<= m_nodes.count() ) { return 0; } return m_nodes.at( number ); } int Node::indexOf( const Node *node ) const { return m_nodes.indexOf( const_cast(node) ); } Duration *Node::getDelay() { /* TODO Calculate the delay of this node. Use the calculated startTime and the set startTime. */ return 0L; } void Node::addDependChildNode( Node *node, Relation::Type p) { addDependChildNode(node,p,Duration()); } void Node::addDependChildNode( Node *node, Relation::Type p, Duration lag) { Relation *relation = new Relation(this, node, p, lag); if (node->addDependParentNode(relation)) m_dependChildNodes.append(relation); else delete relation; } void Node::insertDependChildNode( unsigned int index, Node *node, Relation::Type p) { Relation *relation = new Relation(this, node, p, Duration()); if (node->addDependParentNode(relation)) m_dependChildNodes.insert(index, relation); else delete relation; } bool Node::addDependChildNode( Relation *relation) { if(m_dependChildNodes.indexOf(relation) != -1) return false; m_dependChildNodes.append(relation); return true; } void Node::takeDependChildNode( Relation *rel ) { int i = m_dependChildNodes.indexOf(rel); if ( i != -1 ) { //debugPlan<addDependChildNode(relation)) m_dependParentNodes.append(relation); else delete relation; } void Node::insertDependParentNode( unsigned int index, Node *node, Relation::Type p) { Relation *relation = new Relation(this, node, p, Duration()); if (node->addDependChildNode(relation)) m_dependParentNodes.insert(index,relation); else delete relation; } bool Node::addDependParentNode( Relation *relation) { if(m_dependParentNodes.indexOf(relation) != -1) return false; m_dependParentNodes.append(relation); return true; } void Node::takeDependParentNode( Relation *rel ) { int i = m_dependParentNodes.indexOf(rel); if ( i != -1 ) { //debugPlan<( node ) ) != -1) return true; QListIterator nit(childNodeIterator()); while (nit.hasNext()) { if (nit.next()->isParentOf(node)) return true; } return false; } Relation *Node::findParentRelation( const Node *node ) const { for (int i=0; iparent() == node) return rel; } return (Relation *)0; } Relation *Node::findChildRelation( const Node *node) const { for (int i=0; ichild() == node) return rel; } return (Relation *)0; } Relation *Node::findRelation( const Node *node ) const { Relation *rel = findParentRelation(node); if (!rel) rel = findChildRelation(node); return rel; } bool Node::isDependChildOf( const Node *node ) const { //debugPlan<<" '"<name()<<"'"; for (int i=0; iparent() == node) return true; if (rel->parent()->isDependChildOf(node)) return true; } return false; } QList Node::getParentNodes() { this->m_parentNodes.clear(); foreach(Relation * currentRelation, this->dependParentNodes()) { if (!this->m_parentNodes.contains(currentRelation->parent())) { this->m_parentNodes.append(currentRelation->parent()); } } return this->m_parentNodes; } bool Node::canMoveTo( const Node *newParent ) const { if ( m_parent == newParent ) { return true; } if ( newParent->isChildOf( this ) ) { return false; } if ( isDependChildOf( newParent ) || newParent->isDependChildOf( this ) ) { debugPlan<<"Can't move, node is dependent on new parent"; return false; } foreach ( Node *n, m_nodes ) { if ( !n->canMoveTo( newParent ) ) { return false; } } return true; } void Node::makeAppointments() { QListIterator nit(m_nodes); while (nit.hasNext()) { nit.next()->makeAppointments(); } } void Node::calcResourceOverbooked() { QListIterator nit(m_nodes); while (nit.hasNext()) { nit.next()->calcResourceOverbooked(); } } // Returns the (previously) calculated duration Duration Node::duration( long id ) const { Schedule *s = schedule( id ); return s ? s->duration : Duration::zeroDuration; } double Node::variance( long id, Duration::Unit unit ) const { double d = deviation( id, unit ); return d * d; } double Node::deviation( long id, Duration::Unit unit ) const { Schedule *s = schedule( id ); double d = 0.0; if ( s && m_estimate ) { d = s->duration.toDouble( unit ); double o = ( d * ( 100 + m_estimate->optimisticRatio() ) ) / 100; double p = ( d * ( 100 + m_estimate->pessimisticRatio() ) ) / 100; d = ( p - o ) / 6; } return d; } DateTime Node::startTime( long id ) const { Schedule *s = schedule( id ); return s ? s->startTime : DateTime(); } DateTime Node::endTime( long id ) const { Schedule *s = schedule( id ); return s ? s->endTime : DateTime(); } DateTime Node::appointmentStartTime( long id ) const { Schedule *s = schedule( id ); return s ? s->appointmentStartTime() : DateTime(); } DateTime Node::appointmentEndTime( long id ) const { Schedule *s = schedule( id ); return s ? s->appointmentEndTime() : DateTime(); } void Node::setDuration(const Duration &duration, long id ) { Schedule *s = schedule( id ); if ( s ) { s->duration = duration; } } void Node::setEarlyStart(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->earlyStart = dt; } DateTime Node::earlyStart( long id ) const { Schedule *s = schedule( id ); return s ? s->earlyStart : DateTime(); } void Node::setLateStart(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->lateStart = dt; } DateTime Node::lateStart( long id ) const { Schedule *s = schedule( id ); return s ? s->lateStart : DateTime(); } void Node::setEarlyFinish(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->earlyFinish = dt; } DateTime Node::earlyFinish( long id ) const { Schedule *s = schedule( id ); return s ? s->earlyFinish : DateTime(); } void Node::setLateFinish(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->lateFinish = dt; } DateTime Node::lateFinish( long id ) const { Schedule *s = schedule( id ); return s ? s->lateFinish : DateTime(); } DateTime Node::workStartTime( long id ) const { Schedule *s = schedule( id ); return s ? s->workStartTime : DateTime(); } void Node::setWorkStartTime(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->workStartTime = dt; } DateTime Node::workEndTime( long id ) const { Schedule *s = schedule( id ); return s ? s->workEndTime : DateTime(); } void Node::setWorkEndTime(const DateTime &dt, long id ) { Schedule *s = schedule( id ); if ( s ) s->workEndTime = dt; } bool Node::inCriticalPath( long id ) const { Schedule *s = schedule( id ); return s ? s->inCriticalPath : false; } QStringList Node::schedulingStatus( long id, bool trans ) const { Schedule *s = schedule( id ); QStringList lst; if ( s ) { lst = s->state(); } if ( lst.isEmpty() ) { lst.append( trans ? i18n( "Not scheduled" ) : QString( "Not scheduled" ) ); } return lst; } bool Node::resourceError( long id ) const { Schedule *s = schedule( id ); return s ? s->resourceError : false; } bool Node::resourceOverbooked( long id ) const { Schedule *s = schedule( id ); return s ? s->resourceOverbooked : false; } bool Node::resourceNotAvailable( long id ) const { Schedule *s = schedule( id ); return s ? s->resourceNotAvailable : false; } bool Node::constraintError( long id ) const { Schedule *s = schedule( id ); return s ? s->constraintError : false; } bool Node::schedulingError( long id ) const { Schedule *s = schedule( id ); return s ? s->schedulingError : false; } bool Node::notScheduled( long id ) const { if ( type() == Type_Summarytask ) { // i am scheduled if al least on child is scheduled foreach ( Node *n, m_nodes ) { if ( ! n->notScheduled( id ) ) { return false; } } return true; } Schedule *s = schedule( id ); return s == 0 || s->isDeleted() || s->notScheduled; } QStringList Node::overbookedResources( long id ) const { Schedule *s = schedule( id ); return s ? s->overbookedResources() : QStringList(); } void Node::saveWorkPackageXML( QDomElement &, long ) const { return; } -void Node::saveRelations(QDomElement &element) const +void Node::saveRelations(QDomElement &element, const XmlSaveContext &context) const { + if (!context.saveNode(this)) { + return; + } QListIterator it(m_dependChildNodes); while (it.hasNext()) { - it.next()->save(element); + Relation *r = it.next(); + if (context.saveNode(r->child())) { + r->save(element); + } } QListIterator nodes(m_nodes); while (nodes.hasNext()) { - nodes.next()->saveRelations(element); + nodes.next()->saveRelations(element, context); } } void Node::setConstraint(Node::ConstraintType type) { m_constraint = type; changed( this ); } void Node::setConstraint(const QString &type) { // Do not i18n these, they are used in load() if (type == "ASAP") setConstraint(ASAP); else if (type == "ALAP") setConstraint(ALAP); else if (type == "MustStartOn") setConstraint(MustStartOn); else if (type == "MustFinishOn") setConstraint(MustFinishOn); else if (type == "StartNotEarlier") setConstraint(StartNotEarlier); else if (type == "FinishNotLater") setConstraint(FinishNotLater); else if (type == "FixedInterval") setConstraint(FixedInterval); else setConstraint(ASAP); // default } QString Node::constraintToString( bool trans ) const { return constraintList( trans ).at( m_constraint ); } QStringList Node::constraintList( bool trans ) { // keep theses in the same order as the enum! return QStringList() << (trans ? i18n("As Soon As Possible") : QString("ASAP")) << (trans ? i18n("As Late As Possible") : QString("ALAP")) << (trans ? i18n("Must Start On") : QString("MustStartOn")) << (trans ? i18n("Must Finish On") : QString("MustFinishOn")) << (trans ? i18n("Start Not Earlier") : QString("StartNotEarlier")) << (trans ? i18n("Finish Not Later") : QString("FinishNotLater")) << (trans ? i18n("Fixed Interval") : QString("FixedInterval")); } void Node::propagateEarliestStart(DateTime &time) { if (m_currentSchedule == 0) { return; } if ( type() != Type_Project ) { m_currentSchedule->earlyStart = time; if ( m_currentSchedule->lateStart.isValid() && m_currentSchedule->lateStart < time ) { m_currentSchedule->lateStart = time; } //m_currentSchedule->logDebug( "propagateEarliestStart: " + time.toString() ); switch ( m_constraint ) { case FinishNotLater: case MustFinishOn: if ( m_constraintEndTime < time ) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: end constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; case MustStartOn: case FixedInterval: if ( m_constraintStartTime < time ) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; default: break; } } //debugPlan<earlyStart; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->propagateEarliestStart(time); } } void Node::propagateLatestFinish(DateTime &time) { if (m_currentSchedule == 0) { return; } if ( type() != Type_Project ) { m_currentSchedule->lateFinish = time; if ( m_currentSchedule->earlyFinish.isValid() && m_currentSchedule->earlyFinish > time ) { m_currentSchedule->earlyFinish = time; } switch ( m_constraint ) { case StartNotEarlier: case MustStartOn: if ( m_constraintStartTime > time ) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; case MustFinishOn: case FixedInterval: if ( m_constraintEndTime > time ) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: end constraint %2 > %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; default: break; } } //debugPlan<lateFinish; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->propagateLatestFinish(time); } } void Node::moveEarliestStart(DateTime &time) { if (m_currentSchedule == 0) return; if (m_currentSchedule->earlyStart < time) { //m_currentSchedule->logDebug( "moveEarliestStart: " + m_currentSchedule->earlyStart.toString() + " -> " + time.toString() ); m_currentSchedule->earlyStart = time; } QListIterator it = m_nodes; while (it.hasNext()) { it.next()->moveEarliestStart(time); } } void Node::moveLatestFinish(DateTime &time) { if (m_currentSchedule == 0) return; if (m_currentSchedule->lateFinish > time) m_currentSchedule->lateFinish = time; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->moveLatestFinish(time); } } void Node::initiateCalculation(MainSchedule &sch) { m_visitedForward = false; m_visitedBackward = false; m_durationForward = Duration::zeroDuration; m_durationBackward = Duration::zeroDuration; m_earlyStart = DateTime(); m_earlyFinish = DateTime(); m_lateFinish = DateTime(); QListIterator it = m_nodes; while (it.hasNext()) { it.next()->initiateCalculation(sch); } } void Node::resetVisited() { m_visitedForward = false; m_visitedBackward = false; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->resetVisited(); } } Node *Node::siblingBefore() { //debugPlan; if (parentNode()) return parentNode()->childBefore(this); return 0; } Node *Node::childBefore(Node *node) { //debugPlan; int index = m_nodes.indexOf(node); if (index > 0){ return m_nodes.at(index-1); } return 0; } Node *Node::siblingAfter() { //debugPlan; if (parentNode()) return parentNode()->childAfter(this); return 0; } Node *Node::childAfter(Node *node) { //debugPlan; Q_ASSERT( m_nodes.contains( node ) ); int index = m_nodes.indexOf(node); if (index < m_nodes.count()-1) { return m_nodes.at(index+1); } return 0; } bool Node::moveChildUp(Node* node) { if (findChildNode(node) == -1) return false; // not my node! Node *sib = node->siblingBefore(); if (!sib) return false; sib = sib->siblingBefore(); takeChildNode(node); if (sib) { addChildNode(node, sib); } else { insertChildNode(0, node); } return true; } bool Node::moveChildDown(Node* node) { if (findChildNode(node) == -1) return false; // not my node! Node *sib = node->siblingAfter(); if (!sib) return false; takeChildNode(node); addChildNode(node, sib); return true; } bool Node::legalToLink( const Node *node ) const { Node *p = const_cast(this)->projectNode(); if (p) return p->legalToLink(this, node); return false; } bool Node::isEndNode() const { return m_dependChildNodes.isEmpty(); } bool Node::isStartNode() const { return m_dependParentNodes.isEmpty(); } void Node::setId(const QString& id) { //debugPlan<startTime = startTime; } void Node::setEndTime(const DateTime &endTime, long id ) { Schedule *s = schedule( id ); if ( s ) s->endTime = endTime; } void Node::saveAppointments(QDomElement &element, long id) const { //debugPlan<id()<<","<add(appointment); } void Node::addAppointment(ResourceSchedule *resource, const DateTime &start, const DateTime &end, double load) { Schedule *node = findSchedule(resource->id()); if (node == 0) { node = createSchedule(resource->parent()); } node->setCalculationMode( resource->calculationMode() ); node->addAppointment(resource, start, end, load); } bool Node::isBaselined( long id ) const { Schedule *s = schedule( id ); return s ? s->isBaselined() : false; } void Node::takeSchedule(const Schedule *schedule) { if (schedule == 0) return; if (m_currentSchedule == schedule) m_currentSchedule = 0; m_schedules.take(schedule->id()); } void Node::addSchedule(Schedule *schedule) { if (schedule == 0) return; m_schedules.insert(schedule->id(), schedule); } Schedule *Node::createSchedule(const QString& name, Schedule::Type type, long id) { //debugPlan<removeStartup( *this ); } m_startupAccount = acc; changed(); } void Node::setShutdownCost(double cost) { m_shutdownCost = cost; changed(ShutdownCost); } void Node::setShutdownAccount(Account *acc) { //debugPlan<removeShutdown( *this ); } m_shutdownAccount = acc; changed(); } void Node::setRunningAccount(Account *acc) { //debugPlan<removeRunning( *this ); } m_runningAccount = acc; changed(); } void Node::blockChanged(bool on) { m_blockChanged = on; } void Node::changed(Node *node, int property) { if (m_blockChanged) { return; } switch ( property) { case Type: case StartupCost: case ShutdownCost: case CompletionEntry: case CompletionStarted: case CompletionFinished: case CompletionStartTime: case CompletionFinishTime: case CompletionPercentage: case CompletionRemainingEffort: case CompletionActualEffort: case CompletionUsedEffort: foreach ( Schedule *s, m_schedules ) { s->clearPerformanceCache(); } break; default: break; } if (m_parent) { m_parent->changed(node, property); } } Duration Node::plannedEffort( const Resource *resource, long id, EffortCostCalculationType type ) const { Duration e; foreach ( Node *n, m_nodes ) { e += n->plannedEffort( resource, id, type ); } return e; } Duration Node::plannedEffort( const Resource *resource, QDate date, long id, EffortCostCalculationType type ) const { Duration e; foreach ( Node *n, m_nodes ) { e += n->plannedEffort( resource, date, id, type ); } return e; } Duration Node::plannedEffortTo( const Resource *resource, QDate date, long id, EffortCostCalculationType type ) const { Duration e; foreach ( Node *n, m_nodes ) { e += n->plannedEffortTo( resource, date, id, type ); } return e; } EffortCost Node::plannedCost( long id, EffortCostCalculationType type ) const { EffortCost ec; foreach ( Node *n, m_nodes ) { ec += n->plannedCost( id, type ); } return ec; } EffortCostMap Node::bcwsPrDay( long int id, EffortCostCalculationType type ) const { return const_cast( this )->bcwsPrDay( id, type ); } EffortCostMap Node::bcwsPrDay( long int id, EffortCostCalculationType type ) { Schedule *s = schedule( id ); if ( s == 0 ) { return EffortCostMap(); } EffortCostCache &ec = s->bcwsPrDayCache( type ); if ( ! ec.cached ) { ec.effortcostmap = EffortCostMap(); foreach ( Node *n, m_nodes ) { ec.effortcostmap += n->bcwsPrDay( id, type ); } ec.cached = true; } return ec.effortcostmap; } EffortCostMap Node::bcwpPrDay( long int id, EffortCostCalculationType type ) const { return const_cast( this )->bcwpPrDay( id, type); } EffortCostMap Node::bcwpPrDay( long int id, EffortCostCalculationType type ) { Schedule *s = schedule( id ); if ( s == 0 ) { return EffortCostMap(); } EffortCostCache &ec = s->bcwpPrDayCache( type ); if ( ! ec.cached ) { ec.effortcostmap = EffortCostMap(); foreach ( Node *n, m_nodes ) { ec.effortcostmap += n->bcwpPrDay( id, type ); } ec.cached = true; } return ec.effortcostmap; } EffortCostMap Node::acwp( long id, EffortCostCalculationType type ) const { return const_cast( this )->acwp( id, type ); } EffortCostMap Node::acwp( long id, EffortCostCalculationType type ) { Schedule *s = schedule( id ); if ( s == 0 ) { return EffortCostMap(); } EffortCostCache &ec = s->acwpCache( type ); if ( ! ec.cached ) { ec.effortcostmap = EffortCostMap(); foreach ( Node *n, m_nodes ) { ec.effortcostmap += n->acwp( id, type ); } ec.cached = true; } return ec.effortcostmap; } EffortCost Node::acwp( QDate date, long id ) const { EffortCost ec; foreach ( Node *n, m_nodes ) { ec += n->acwp( date, id ); } return ec; } void Node::slotStandardWorktimeChanged(KPlato::StandardWorktime*) { //debugPlan<m_expectedCached = false; m_estimate->m_optimisticCached = false; m_estimate->m_pessimisticCached = false; } } void Node::emitDocumentAdded( Node *node, Document *doc, int idx ) { if ( m_parent ) { m_parent->emitDocumentAdded( node, doc, idx ); } } void Node::emitDocumentRemoved( Node *node, Document *doc, int idx ) { if ( m_parent ) { m_parent->emitDocumentRemoved( node, doc, idx ); } } void Node::emitDocumentChanged( Node *node, Document *doc, int idx ) { if ( m_parent ) { m_parent->emitDocumentChanged( node, doc, idx ); } } ////////////////////////// Estimate ///////////////////////////////// Estimate::Estimate( Node *parent ) : m_parent( parent ) { m_pertCached = false; setUnit( Duration::Unit_h ); setExpectedEstimate( 8.0 ); setPessimisticEstimate( 8.0 ); setOptimisticEstimate( 8.0 ); m_type = Type_Effort; m_calendar = 0; m_risktype = Risk_None; } Estimate::Estimate(const Estimate &estimate, Node *parent) : m_parent( parent ) { copy( estimate ); } Estimate::~Estimate() { } void Estimate::clear() { m_pertCached = false; setExpectedEstimate( 0.0 ); setPessimisticEstimate( 0.0 ); setOptimisticEstimate( 0.0 ); m_type = Type_Effort; m_calendar = 0; m_risktype = Risk_None; m_unit = Duration::Unit_h; changed(); } Estimate &Estimate::operator=( const Estimate &estimate ) { copy( estimate ); return *this; } void Estimate::copy( const Estimate &estimate ) { //m_parent = 0; // don't touch m_expectedEstimate = estimate.m_expectedEstimate; m_optimisticEstimate = estimate.m_optimisticEstimate; m_pessimisticEstimate = estimate.m_pessimisticEstimate; m_expectedValue = estimate.m_expectedValue; m_optimisticValue = estimate.m_optimisticValue; m_pessimisticValue = estimate.m_pessimisticValue; m_expectedCached = estimate.m_expectedCached; m_optimisticCached = estimate.m_optimisticCached; m_pessimisticCached = estimate.m_pessimisticCached; m_pertExpected = estimate.m_pertExpected; m_pertCached = estimate.m_pertCached; m_type = estimate.m_type; m_calendar = estimate.m_calendar; m_risktype = estimate.m_risktype; m_unit = estimate.m_unit; changed(); } double Estimate::variance() const { double d = deviation(); return d * d; } double Estimate::variance( Duration::Unit unit ) const { double d = deviation( unit ); return d * d; } double Estimate::deviation() const { return ( m_pessimisticEstimate - m_optimisticEstimate ) / 6; } double Estimate::deviation( Duration::Unit unit ) const { if ( unit == m_unit ) { return deviation(); } double p = pessimisticValue().toDouble( unit ); double o = optimisticValue().toDouble( unit ); double v = ( p - o ) / 6; return v; } Duration Estimate::pertExpected() const { if (m_risktype == Risk_Low) { if ( ! m_pertCached ) { m_pertExpected = (optimisticValue() + pessimisticValue() + (expectedValue()*4))/6; m_pertCached = true; } return m_pertExpected; } else if (m_risktype == Risk_High) { if ( ! m_pertCached ) { m_pertExpected = (optimisticValue() + (pessimisticValue()*2) + (expectedValue()*4))/7; m_pertCached = true; } return m_pertExpected; } return expectedValue(); // risk==none } Duration Estimate::pertOptimistic() const { if (m_risktype != Risk_None) { return pertExpected() - Duration( variance( Duration::Unit_ms ) ); } return optimisticValue(); } Duration Estimate::pertPessimistic() const { if (m_risktype != Risk_None) { return pertExpected() + Duration( variance( Duration::Unit_ms ) ); } return pessimisticValue(); } Duration Estimate::value(int valueType, bool pert) const { if (valueType == Estimate::Use_Expected) { return pert ? pertExpected() : expectedValue(); } else if (valueType == Estimate::Use_Optimistic) { return pert ? pertOptimistic() : optimisticValue(); } else if (valueType == Estimate::Use_Pessimistic) { return pert ? pertPessimistic() : pessimisticValue(); } return expectedValue(); } void Estimate::setUnit( Duration::Unit unit ) { m_unit = unit; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(); } bool Estimate::load(KoXmlElement &element, XMLLoaderObject &status) { setType(element.attribute("type")); setRisktype(element.attribute("risk")); if ( status.version() <= "0.6" ) { m_unit = (Duration::Unit)(element.attribute("display-unit", QString().number(Duration::Unit_h) ).toInt()); QList s = status.project().standardWorktime()->scales(); m_expectedEstimate = scale( Duration::fromString(element.attribute("expected")), m_unit, s ); m_optimisticEstimate = scale( Duration::fromString(element.attribute("optimistic")), m_unit, s ); m_pessimisticEstimate = scale( Duration::fromString(element.attribute("pessimistic")), m_unit, s ); } else { if ( status.version() <= "0.6.2" ) { // 0 pos in unit is now Unit_Y, so add 3 to get the correct new unit m_unit = (Duration::Unit)(element.attribute("unit", QString().number(Duration::Unit_ms - 3) ).toInt() + 3); } else { m_unit = Duration::unitFromString( element.attribute( "unit" ) ); } m_expectedEstimate = element.attribute("expected", "0.0").toDouble(); m_optimisticEstimate = element.attribute("optimistic", "0.0").toDouble(); m_pessimisticEstimate = element.attribute("pessimistic", "0.0").toDouble(); m_calendar = status.project().findCalendar(element.attribute("calendar-id")); } return true; } void Estimate::save(QDomElement &element) const { QDomElement me = element.ownerDocument().createElement("estimate"); element.appendChild(me); me.setAttribute("expected", QString::number(m_expectedEstimate)); me.setAttribute("optimistic", QString::number(m_optimisticEstimate)); me.setAttribute("pessimistic", QString::number(m_pessimisticEstimate)); me.setAttribute("type", typeToString()); if ( m_calendar ) { me.setAttribute("calendar-id", m_calendar->id() ); } me.setAttribute("risk", risktypeToString()); me.setAttribute("unit", Duration::unitToString( m_unit ) ); } QString Estimate::typeToString( bool trans ) const { return typeToStringList( trans ).at( m_type ); } QString Estimate::typeToString( Estimate::Type typ, bool trans ) { return typeToStringList( trans ).value( typ ); } QStringList Estimate::typeToStringList( bool trans ) { return QStringList() << (trans ? i18n("Effort") : QString("Effort")) << (trans ? i18n("Duration") : QString("Duration")); } void Estimate::setType(Type type) { m_type = type; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(); } void Estimate::setType(const QString& type) { if (type == "Effort") setType(Type_Effort); else if (type == "Duration" || /*old format*/ type == "FixedDuration") setType(Type_Duration); else if (/*old format*/type == "Length") setType(Type_Duration); else if (type == "Type_FixedDuration") // Typo, keep old xml files working setType(Type_Duration); else setType(Type_Effort); // default } QString Estimate::risktypeToString( bool trans ) const { return risktypeToStringList( trans ).at( m_risktype ); } QStringList Estimate::risktypeToStringList( bool trans ) { return QStringList() << (trans ? i18n("None") : QString("None")) << (trans ? i18n("Low") : QString("Low")) << (trans ? i18n("High") : QString("High")); } void Estimate::setRisktype(const QString& type) { if (type == "High") setRisktype(Risk_High); else if (type == "Low") setRisktype(Risk_Low); else setRisktype(Risk_None); // default } void Estimate::setRisktype(Risktype type) { m_pertCached = false; m_risktype = type; changed(); } void Estimate::setCalendar( Calendar *calendar ) { m_calendar = calendar; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(); } void Estimate::setExpectedEstimate( double value) { m_expectedEstimate = value; m_expectedCached = false; m_pertCached = false; changed(); } void Estimate::setOptimisticEstimate( double value ) { m_optimisticEstimate = value; m_optimisticCached = false; m_pertCached = false; changed(); } void Estimate::setPessimisticEstimate( double value ) { m_pessimisticEstimate = value; m_pessimisticCached = false; m_pertCached = false; changed(); } void Estimate::setOptimisticRatio(int percent) { int p = percent>0 ? -percent : percent; m_optimisticValue = expectedValue()*(100+p)/100; m_optimisticEstimate = scale( m_optimisticValue, m_unit, scales() ); m_optimisticCached = true; m_pertCached = false; changed(); } int Estimate::optimisticRatio() const { if (m_expectedEstimate == 0.0) return 0; return (int)((optimisticValue()*100)/expectedValue())-100; } void Estimate::setPessimisticRatio(int percent) { int p = percent<0 ? -percent : percent; m_pessimisticValue = expectedValue()*(100+p)/100; m_pessimisticEstimate = scale( m_pessimisticValue, m_unit, scales() ); m_pessimisticCached = true; m_pertCached = false; changed(); } int Estimate::pessimisticRatio() const { if (m_expectedEstimate == 0.0) return 0; return (int)((pessimisticValue()*100)/expectedValue())-100; } // internal void Estimate::setOptimisticValue() { m_optimisticValue = scale( m_optimisticEstimate, m_unit, scales() ); m_optimisticCached = true; m_pertCached = false; } // internal void Estimate::setExpectedValue() { m_expectedValue = scale( m_expectedEstimate, m_unit, scales() ); m_expectedCached = true; m_pertCached = false; } // internal void Estimate::setPessimisticValue() { m_pessimisticValue = scale( m_pessimisticEstimate, m_unit, scales() ); m_pessimisticCached = true; m_pertCached = false; } Duration Estimate::optimisticValue() const { if ( ! m_optimisticCached ) { const_cast(this)->setOptimisticValue(); } return m_optimisticValue; } Duration Estimate::pessimisticValue() const { if ( ! m_pessimisticCached ) { const_cast(this)->setPessimisticValue(); } return m_pessimisticValue; } Duration Estimate::expectedValue() const { if ( ! m_expectedCached ) { const_cast(this)->setExpectedValue(); } return m_expectedValue; } double Estimate::scale( const Duration &value, Duration::Unit unit, const QList &scales ) { //debugPlan< lst = scales; switch ( lst.count() ) { case Duration::Unit_Y: lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year Q_FALLTHROUGH(); case Duration::Unit_M: lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month Q_FALLTHROUGH(); case Duration::Unit_w: lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week Q_FALLTHROUGH(); case Duration::Unit_d: lst << 24 * 60 * 60 * 1000; // add milliseconds in day Q_FALLTHROUGH(); case Duration::Unit_h: lst << 60 * 60 * 1000; // add milliseconds in hour case Duration::Unit_m: lst << 60 * 1000; // add milliseconds in minute Q_FALLTHROUGH(); case Duration::Unit_s: lst << 1000; // add milliseconds in second Q_FALLTHROUGH(); case Duration::Unit_ms: lst << 1; // add milliseconds in a millisecond Q_FALLTHROUGH(); default: break; } double v = ( double )( value.milliseconds() ); v /= lst[ unit ]; //debugPlan< &scales ) { //debugPlan< lst = scales; switch ( lst.count() ) { case Duration::Unit_Y: lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year Q_FALLTHROUGH(); case Duration::Unit_M: lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month Q_FALLTHROUGH(); case Duration::Unit_w: lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week Q_FALLTHROUGH(); case Duration::Unit_d: lst << 24 * 60 * 60 * 1000; // add milliseconds in day Q_FALLTHROUGH(); case Duration::Unit_h: lst << 60 * 60 * 1000; // add milliseconds in hour Q_FALLTHROUGH(); case Duration::Unit_m: lst << 60 * 1000; // add milliseconds in minute Q_FALLTHROUGH(); case Duration::Unit_s: lst << 1000; // add milliseconds in second Q_FALLTHROUGH(); case Duration::Unit_ms: lst << 1; // add milliseconds in a millisecond Q_FALLTHROUGH(); default: break; } qint64 v = ( qint64 )( value * lst[ unit ] ); //debugPlan< Estimate::defaultScales() { QList lst; lst << (qint64)(365 * 24) * 60 * 60 * 1000 // add milliseconds in a year << (qint64)(30 * 24) * 60 * 60 * 1000 // add milliseconds in a month << (qint64)(7 * 24) * 60 * 60 * 1000 // add milliseconds in a week << 24 * 60 * 60 * 1000 // add milliseconds in day << 60 * 60 * 1000 // add milliseconds in hour << 60 * 1000 // add milliseconds in minute << 1000 // add milliseconds in second << 1; // add milliseconds in a millisecond return lst; } QList Estimate::scales() const { QList s; if ( m_type == Type_Duration && m_calendar == 0 ) { return s; // Use default scaling ( 24h a day...) } if ( m_parent == 0 ) { return s; } Project *p = static_cast( m_parent->projectNode() ); if ( p == 0 ) { return s; } s << p->standardWorktime()->scales(); return s; } } //KPlato namespace diff --git a/src/libs/kernel/kptnode.h b/src/libs/kernel/kptnode.h index 87cce180..3cc3d3a0 100644 --- a/src/libs/kernel/kptnode.h +++ b/src/libs/kernel/kptnode.h @@ -1,880 +1,880 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004 - 2011 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KPTNODE_H #define KPTNODE_H #include "plankernel_export.h" #include "kptglobal.h" #include "kptrelation.h" #include "kptduration.h" #include "kptdatetime.h" #include "kptschedule.h" #include "kptdocuments.h" #include "kptresource.h" #include #include #include #include class QDomElement; /// The main namespace. namespace KPlato { class Account; class Project; class Appointment; class ResourceGroup; class Resource; class ResourceGroupRequest; class Estimate; class EffortCostMap; class EffortCost; class Calendar; class KPlatoXmlLoaderBase; class XmlSaveContext; /** * This class represents any node in the project, a node can be a project or * a subproject or any task. * This class is basically an abstract interface to make the design more OO. */ class PLANKERNEL_EXPORT Node : public QObject { Q_OBJECT public: enum ConstraintType { ASAP, ALAP, MustStartOn, MustFinishOn, StartNotEarlier, FinishNotLater, FixedInterval }; enum State { State_None = 0, State_Started = 1, State_StartedLate = 2, State_StartedEarly = 4, State_Finished = 8, State_FinishedLate = 16, State_FinishedEarly = 32, State_Running = 64, State_RunningLate = 128, State_RunningEarly = 256, State_ReadyToStart = 512, // all preceding tasks finished (if any) State_NotReadyToStart = 1024, // all preceding tasks not finished (must be one or more) State_NotScheduled = 2048, State_Late = 4096 }; enum Properties { Type, StartupCost, ShutdownCost, CompletionEntry, CompletionStarted, CompletionFinished, CompletionStartTime, CompletionFinishTime, CompletionPercentage, CompletionRemainingEffort, CompletionActualEffort, CompletionUsedEffort }; explicit Node(Node *parent = 0); Node(const Node &node, Node *parent = 0); // Declare the class abstract virtual ~Node() = 0; void setId(const QString& id); QString id() const { return m_id; } // unique identity enum NodeTypes { Type_Node = 0, Type_Project = 1, Type_Subproject = 2, Type_Task = 3, Type_Milestone = 4, Type_Periodic = 5, Type_Summarytask = 6 }; virtual int type() const = 0; QString typeToString( bool trans = false ) const; static QString typeToString( NodeTypes typ, bool trans = false ); static QStringList typeToStringList( bool trans ); /** * Returns a pointer to the project node (main- or sub-project) * Returns 0 if no project exists. */ virtual Node *projectNode(); /** * Returns a pointer to the project node (main- or sub-project) * Returns 0 if no project exists. */ virtual const Node *projectNode() const; // The load and save methods virtual bool load(KoXmlElement &, XMLLoaderObject &) { return true; } virtual void save(QDomElement &element, const XmlSaveContext &context) const = 0; /// Save me and my childrens relations. - virtual void saveRelations(QDomElement &element) const; + virtual void saveRelations(QDomElement &element, const XmlSaveContext &context) const; /// Save a workpackage document containing @p node with schedule identity @p id virtual void saveWorkPackageXML( QDomElement &element, long id ) const; // simple child node management // Child nodes are things like subtasks, basically a task can exists of // several sub-tasks. Creating a table has 4 subtasks, 1) measuring // 2) cutting 3) building 4) painting. Node *parentNode() const { return m_parent; } void setParentNode( Node* newParent ) { m_parent = newParent;} const QList &childNodeIterator() const { return m_nodes; } int numChildren() const { return m_nodes.count(); } virtual void addChildNode(Node *node, Node *after=0); virtual void insertChildNode(int index, Node *node); void takeChildNode(Node *node ); void takeChildNode(int number ); Node* childNode(int number); const Node* childNode(int number) const; int findChildNode( const Node* node ) const; bool isChildOf( const Node *node ) const; int indexOf( const Node *node ) const; // Time-dependent child-node-management. // list all nodes that are dependent upon this one. // Building a house requires the table to be finished, therefore the // house-building is time dependent on the table-building. So a child // of the table-building node is the house-building node. int numDependChildNodes() const { return m_dependChildNodes.count(); } /// Adds relation of type @p p to both this node and @p node virtual void addDependChildNode( Node *node, Relation::Type p=Relation::FinishStart); /// Adds relation of type @p p with @p lag to both this node and @p node virtual void addDependChildNode( Node *node, Relation::Type p, Duration lag); /// Adds @p relation only to this node virtual bool addDependChildNode( Relation *relation); /// Inserts relation to this node at index @p index and appends relation to @p node virtual void insertDependChildNode( unsigned int index, Node *node, Relation::Type p=Relation::FinishStart); /** * Takes the relation rel from this node only. */ void takeDependChildNode( Relation *rel ); Relation *getDependChildNode( int number ) const { return m_dependChildNodes.at(number); } QList dependChildNodes() const { return m_dependChildNodes; } int numDependParentNodes() const { return m_dependParentNodes.count(); } /// Adds relation if type @p to both this node and @p node virtual void addDependParentNode(Node *node, Relation::Type p=Relation::FinishStart); /// Adds relation to both this node and @p node virtual void addDependParentNode( Node *node, Relation::Type p, Duration lag); /// Adds relation only to this node virtual bool addDependParentNode( Relation *relation); /// Inserts relation to this node at index and appends relation to @p node virtual void insertDependParentNode( unsigned int index, Node *node, Relation::Type p=Relation::FinishStart); /** * Takes the relation @p rel from this node only. */ void takeDependParentNode( Relation *rel ); Relation *getDependParentNode( int number ) const { return m_dependParentNodes.at(number); } QList dependParentNodes() const { return m_dependParentNodes; } QList getParentNodes(); bool isParentOf( const Node *node ) const; bool isDependChildOf( const Node *node ) const; virtual bool canMoveTo( const Node *newParent ) const; Relation *findParentRelation( const Node *node) const; Relation *findChildRelation( const Node *node ) const; Relation *findRelation( const Node *node ) const; /// Set the scheduled start time void setStartTime(const DateTime &startTime, long id = CURRENTSCHEDULE ); /// Return the scheduled start time virtual DateTime startTime( long id = CURRENTSCHEDULE ) const; /// Set the scheduled end time void setEndTime(const DateTime &endTime, long id = CURRENTSCHEDULE ); /// Return the scheduled end time virtual DateTime endTime( long id = CURRENTSCHEDULE ) const; /// Set the scheduled duration void setDuration(const Duration &duration, long id = CURRENTSCHEDULE ); DateTime appointmentStartTime( long id = CURRENTSCHEDULE) const; DateTime appointmentEndTime( long id = CURRENTSCHEDULE ) const; /// Return the estimate for this node Estimate *estimate() const { return m_estimate; } /** * Instead of using the expected duration, generate a random value using * the Distribution of each Task. This can be used for Monte-Carlo * estimation of Project duration. */ virtual Duration *getRandomDuration() = 0; /** * Calculate the delay of this node. * It is the difference between the actual startTime and scheduled startTime. */ Duration *getDelay(); // TODO QString &name() { return m_name; } QString &leader() { return m_leader; } QString &description() { return m_description; } const QString &name() const { return m_name; } const QString &leader() const { return m_leader; } const QString &description() const { return m_description; } void setName(const QString &n); void setLeader(const QString &l); void setDescription(const QString &d); void setConstraint(Node::ConstraintType type); void setConstraint(const QString &type); int constraint() const { return m_constraint; } QString constraintToString( bool trans=false ) const; static QStringList constraintList( bool trans ); virtual void setConstraintStartTime(const DateTime &time) { m_constraintStartTime = time; changed( this ); } virtual void setConstraintEndTime(const DateTime &time) { m_constraintEndTime = time; changed( this ); } virtual DateTime constraintStartTime() const { return m_constraintStartTime; } virtual DateTime constraintEndTime() const { return m_constraintEndTime; } virtual DateTime startNotEarlier() const { return m_constraintStartTime; } virtual DateTime finishNotLater() const { return m_constraintEndTime; } virtual DateTime mustStartOn() const { return m_constraintStartTime; } virtual DateTime mustFinishOn() const { return m_constraintEndTime; } virtual ResourceGroupRequest *resourceGroupRequest(const ResourceGroup * /*group*/) const { return 0; } virtual QStringList requestNameList() const { return QStringList(); } virtual bool containsRequest( const QString &/*identity*/ ) const { return false; } virtual ResourceRequest *resourceRequest( const QString &/*name*/ ) const { return 0; } /// Return the list of resources assigned to this task virtual QStringList assignedNameList( long /*id*/ = CURRENTSCHEDULE ) const { return QStringList(); } virtual void makeAppointments(); /// Calculates if the assigned resource is overbooked /// within the duration of this node virtual void calcResourceOverbooked(); /// Return the scheduling status of schedule @p id. If @p trans is true, text is translated QStringList schedulingStatus( long id, bool trans = false ) const; /// EstimateType == Estimate, but no resource is requested bool resourceError( long id = CURRENTSCHEDULE ) const; /// The assigned resource is overbooked virtual bool resourceOverbooked( long id = CURRENTSCHEDULE ) const; /// The requested resource is not available bool resourceNotAvailable( long id = CURRENTSCHEDULE ) const; /// The task cannot be scheduled to fulfil all the constraints virtual bool constraintError( long id = CURRENTSCHEDULE ) const; /// The task cannot be scheduled correctly virtual bool schedulingError( long id = CURRENTSCHEDULE ) const; /// The node has not been scheduled bool notScheduled( long id = CURRENTSCHEDULE ) const; /// Return a list of overbooked resources virtual QStringList overbookedResources( long id = CURRENTSCHEDULE ) const; /// The assigned resources can not fulfil the estimated effort. virtual bool effortMetError( long /*id*/ = CURRENTSCHEDULE ) const { return false; } virtual EffortCostMap plannedEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const=0; virtual EffortCostMap plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const=0; /// Returns the total planned effort for @p resource on this task (or subtasks) virtual Duration plannedEffort( const Resource *resource, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns the total planned effort for this task (or subtasks) virtual Duration plannedEffort( long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const { Q_UNUSED(id); return Duration::zeroDuration; } /// Returns the total planned effort for this task (or subtasks) on date virtual Duration plannedEffort(QDate , long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const { Q_UNUSED(id); return Duration::zeroDuration; } /// Returns the total planned effort for @p resource on this task (or subtasks) on date virtual Duration plannedEffort( const Resource *resource, QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns the planned effort up to and including date virtual Duration plannedEffortTo(QDate , long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const { Q_UNUSED(id); return Duration::zeroDuration; } /// Returns the planned effort for @p resource up to and including date virtual Duration plannedEffortTo( const Resource *resource, QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns the total actual effort for this task (or subtasks) virtual Duration actualEffort() const { return Duration::zeroDuration; } /// Returns the total actual effort for this task (or subtasks) on date virtual Duration actualEffort(QDate /*date*/ ) const { return Duration::zeroDuration; } /// Returns the total actual effort for this task (or subtasks) up to and including date virtual Duration actualEffortTo(QDate /*date*/ ) const { return Duration::zeroDuration; } virtual EffortCostMap actualEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const=0; /// Returns the actual effort and cost pr day used by @p resource virtual EffortCostMap actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const=0; /** * Planned cost is the sum total of all resources and other costs * planned for this node. */ virtual EffortCost plannedCost( long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /** * Planned cost from start of activity up to and including date * is the sum of all resource costs and other costs planned for this node. */ virtual double plannedCostTo(QDate /*date*/, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const { Q_UNUSED(id); return 0; } /// Actual cost up to and including date virtual EffortCost actualCostTo( long int /*id*/, QDate /*date*/) const { return EffortCost(); } /// Returns the effort planned to be used to reach the actual percent finished virtual Duration budgetedWorkPerformed( QDate , long = CURRENTSCHEDULE ) const = 0; /// Returns the cost planned to be used to reach the actual percent finished virtual double budgetedCostPerformed( QDate , long = CURRENTSCHEDULE ) const { return 0.0; }; /// Return map of Budgeted Cost of Work Scheduled pr day virtual EffortCostMap bcwsPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Return map of Budgeted Cost of Work Scheduled pr day virtual EffortCostMap bcwsPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ) const; /// Budgeted Cost of Work Scheduled virtual double bcws( QDate /*date*/, long id = CURRENTSCHEDULE ) const { Q_UNUSED(id); return 0.0; } /// Return map of Budgeted Cost of Work Scheduled pr day (also includes bcws pr day) virtual EffortCostMap bcwpPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Return map of Budgeted Cost of Work Scheduled pr day (also includes bcws pr day) virtual EffortCostMap bcwpPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ) const; /// Budgeted Cost of Work Performed virtual double bcwp( long id ) const { Q_UNUSED(id); return 0.0; } /// Budgeted Cost of Work Performed ( up to @p date ) virtual double bcwp( QDate /*date*/, long id = CURRENTSCHEDULE ) const { Q_UNUSED(id); return 0.0; } /// Return a map of Actual effort and Cost of Work Performed virtual EffortCostMap acwp( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Return a map of Actual effort and Cost of Work Performed virtual EffortCostMap acwp( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ) const; /// Return Actual effort and Cost of Work Performed upto @date virtual EffortCost acwp( QDate date, long id = CURRENTSCHEDULE ) const; /// Effort based performance index virtual double effortPerformanceIndex(QDate /*date*/, long /*id*/ = CURRENTSCHEDULE ) const { return 0.0; } /// Schedule performance index virtual double schedulePerformanceIndex(QDate /*date*/, long /*id*/ = CURRENTSCHEDULE ) const { return 0.0; } /// Cost performance index virtual double costPerformanceIndex( long int /*id*/, QDate /*date*/, bool * /*error=0*/ ) const { return 0.0; } virtual void initiateCalculationLists(MainSchedule &sch) = 0; virtual DateTime calculateForward(int /*use*/) = 0; virtual DateTime calculateEarlyFinish(int /*use*/) { return DateTime(); } virtual DateTime calculateBackward(int /*use*/) = 0; virtual DateTime calculateLateStart(int /*use*/) { return DateTime(); } virtual DateTime scheduleForward(const DateTime &, int /*use*/) = 0; virtual DateTime scheduleFromStartTime(int /*use*/) { return DateTime(); } virtual DateTime scheduleBackward(const DateTime &, int /*use*/) = 0; virtual DateTime scheduleFromEndTime(int /*use*/) { return DateTime(); } virtual void adjustSummarytask() = 0; /// Returns the (previously) calculated duration Duration duration( long id = CURRENTSCHEDULE ) const; /** * The variance is calculated based on * the optimistic/pessimistic ratio specified for the estimate. */ double variance( long id = CURRENTSCHEDULE, Duration::Unit unit = Duration::Unit_ms ) const; /** * The standard deviation is calculated based on * the optimistic/pessimistic ratio specified for the estimate. */ double deviation( long id = CURRENTSCHEDULE, Duration::Unit unit = Duration::Unit_ms ) const; Node *siblingBefore(); Node *childBefore(Node *node); Node *siblingAfter(); Node *childAfter(Node *node); bool moveChildUp(Node *node); bool moveChildDown(Node *node); /// Check if this node can be linked to node bool legalToLink( const Node *node ) const; /// Check if node par can be linked to node child. (Reimplement) virtual bool legalToLink( const Node *, const Node *) const { return false; } /// Save appointments for schedule with id virtual void saveAppointments(QDomElement &element, long id) const; ///Return the list of appointments for schedule with id. QList appointments( long id = CURRENTSCHEDULE ); /// Adds appointment to this node only (not to resource) virtual bool addAppointment(Appointment *appointment, Schedule &main); /// Return list of all resources with appointments to this task for schedule with @p id. QList assignedResources( long id ) const; /// Find the node with my id virtual Node *findNode() const { return findNode(m_id); } /// Find the node with identity id virtual Node *findNode(const QString &id) const { return (m_parent ? m_parent->findNode(id) : 0); } /// Remove myself from the id register virtual bool removeId() { return removeId(m_id); } /// Remove the registered identity @p id virtual bool removeId(const QString &id) { return (m_parent ? m_parent->removeId(id) : false); } /** * This is when work can start on this node in accordance with * the calendar of allocated resources. Normally this is the same * as @ref startTime(), but may differ if timing constraints are set. */ virtual DateTime workStartTime( long id = CURRENTSCHEDULE ) const; void setWorkStartTime(const DateTime &dt, long id = CURRENTSCHEDULE ); /** * This is when work can finish on this node in accordance with * the calendar of allocated resources. Normally this is the same * as @ref endTime(), but may differ if timing constraints are set. */ virtual DateTime workEndTime( long id = CURRENTSCHEDULE ) const; void setWorkEndTime(const DateTime &dt, long id = CURRENTSCHEDULE ); /// Returns true if this node is critical virtual bool isCritical( long id = CURRENTSCHEDULE ) const { Q_UNUSED(id); return false; } /// Returns true if this node is in a critical path virtual bool inCriticalPath( long id = CURRENTSCHEDULE ) const; /// Returns the level this node is in the hierarchy. Top node is level 0. virtual int level() const; /// Generate WBS Code virtual QString generateWBSCode( QList &indexes, bool sortable = false ) const; /// Returns the Work Breakdown Structure Code QString wbsCode(bool sortable = false) const; double startupCost() const { return m_startupCost; } void setStartupCost(double cost); Account *startupAccount() const { return m_startupAccount; } void setStartupAccount(Account *acc); double shutdownCost() const { return m_shutdownCost; } void setShutdownCost(double cost); Account *shutdownAccount() const { return m_shutdownAccount; } void setShutdownAccount(Account *acc); Account *runningAccount() const { return m_runningAccount; } void setRunningAccount(Account *acc); bool isBaselined( long int id = BASELINESCHEDULE ) const; /** * Return schedule with @p id * If @p id == CURRENTSCHEDULE, return m_currentSchedule * Return 0 if schedule with @p id doesn't exist. */ Schedule *schedule( long id = CURRENTSCHEDULE ) const; /// Return current schedule Schedule *currentSchedule() const { return m_currentSchedule; } /// Set current schedule to schedule with identity @p id, for me and my children virtual void setCurrentSchedule(long id); /// Return true if this node has a valid schedule with identity == @p id bool isScheduled( long id = CURRENTSCHEDULE ) const; /// Return the list of schedules for this node QHash &schedules() { return m_schedules; } /// Find schedule matching name and type. Does not return deleted schedule. Schedule *findSchedule(const QString &name, const Schedule::Type type); /// Find schedule matching name. Does not return deleted schedule. Schedule *findSchedule(const QString &name); /// Find schedule matching type. Does not return deleted schedule. Schedule *findSchedule(const Schedule::Type type); /// Find schedule matching id. Also returns deleted schedule. Schedule *findSchedule(long id) const; /// Take, don't delete (as in destruct). void takeSchedule(const Schedule *schedule); /// Add schedule to list, replace if schedule with same id already exists. void addSchedule(Schedule *schedule); /// Create a new schedule. Schedule *createSchedule(const QString& name, Schedule::Type type, long id); /// Create a new schedule. Schedule *createSchedule(Schedule *parent); /// Set deleted = onoff for schedule with id void setScheduleDeleted(long id, bool onoff); /// Set parent schedule recursively virtual void setParentSchedule(Schedule *sch); const ResourceRequestCollection &requests() const { return m_requests; } ResourceRequestCollection &requests() { return m_requests; } virtual uint state( long ) const { return State_None; } const Documents &documents() const { return m_documents; } Documents &documents() { return m_documents; } virtual void emitDocumentAdded( Node *node, Document *doc, int idx ); virtual void emitDocumentRemoved( Node *node, Document *doc, int idx ); virtual void emitDocumentChanged( Node *node, Document *doc, int idx ); void blockChanged(bool on = true); public: // These shouldn't be available to other than those who inherits /// Calculate the critical path virtual bool calcCriticalPath(bool fromEnd); virtual void calcFreeFloat(); /// Check if this node has any dependent child nodes virtual bool isEndNode() const; /// Check if this node has any dependent parent nodes virtual bool isStartNode() const; virtual void initiateCalculation(MainSchedule &sch); virtual void resetVisited(); void propagateEarliestStart(DateTime &time); void propagateLatestFinish(DateTime &time); void moveEarliestStart(DateTime &time); void moveLatestFinish(DateTime &time); // Reimplement this virtual Duration summarytaskDurationForward(const DateTime &/*time*/) { return Duration::zeroDuration; } // Reimplement this virtual DateTime summarytaskEarliestStart() { return DateTime(); } // Reimplement this virtual Duration summarytaskDurationBackward(const DateTime &/*time*/) { return Duration::zeroDuration; } // Reimplement this virtual DateTime summarytaskLatestFinish() { return DateTime(); } /** * earlyStart() returns earliest time this node can start * given the constraints of the network. */ DateTime earlyStart( long id = CURRENTSCHEDULE ) const; /** * setEarlyStart() sets earliest time this node can start */ void setEarlyStart(const DateTime &dt, long id = CURRENTSCHEDULE ); /** * lateStart() returns latest time this node can start * given the constraints of the network. */ DateTime lateStart( long id = CURRENTSCHEDULE ) const; /** * setLateStart() sets the earliest time this node can start */ void setLateStart(const DateTime &dt, long id = CURRENTSCHEDULE ); /** * earlyFinish() returns earliest time this node can finish * given the constraints of the network. */ DateTime earlyFinish( long id = CURRENTSCHEDULE ) const; /** * setEarlyFinish() sets earliest time this node can finish */ void setEarlyFinish(const DateTime &dt, long id = CURRENTSCHEDULE ); /** * lateFinish() returns latest time this node can finish * given the constraints of the network. */ DateTime lateFinish( long id = CURRENTSCHEDULE ) const; /** * setLateFinish() sets latest time this node can finish */ void setLateFinish(const DateTime &dt, long id = CURRENTSCHEDULE ); /// Adds appointment to both this node and resource virtual void addAppointment(ResourceSchedule *resource, const DateTime &start, const DateTime &end, double load=100); virtual void clearProxyRelations() {} virtual void addParentProxyRelations( const QList & ) {} virtual void addChildProxyRelations( const QList & ) {} virtual void addParentProxyRelation(Node *, const Relation *) {} virtual void addChildProxyRelation(Node *, const Relation *) {} virtual void changed( int property = -1 ) { changed( this, property ); } Duration getmDurationForward(){ return this->m_durationForward;} public Q_SLOTS: void slotStandardWorktimeChanged(KPlato::StandardWorktime*); protected: friend class KPlatoXmlLoaderBase; /** * Calculates and returns the duration of the node. * Reimplement. */ virtual Duration duration(const DateTime &/*time*/, int /*use*/, bool /*backward*/) { return Duration::zeroDuration; } // NOTE: Cannot use setCurrentSchedule() due to overload/casting problems void setCurrentSchedulePtr(Schedule *schedule) { m_currentSchedule = schedule; } virtual void changed(Node *node, int property = -1 ); QList m_nodes; QList m_dependChildNodes; QList m_dependParentNodes; QListm_parentNodes; Node *m_parent; QString m_id; // unique id QString m_name; // Name of this node QString m_leader; // Person or group responsible for this node QString m_description; // Description of this node Estimate *m_estimate; ConstraintType m_constraint; /** * m_constraintTime is used if any of the constraints * FixedInterval, StartNotEarlier, MustStartOn or FixedInterval is selected */ DateTime m_constraintStartTime; /** * m_constraintEndTime is used if any of the constraints * FixedInterval, FinishNotLater, MustFinishOn or FixedInterval is selected */ DateTime m_constraintEndTime; bool m_visitedForward; bool m_visitedBackward; Duration m_durationForward; Duration m_durationBackward; DateTime m_earlyStart; DateTime m_earlyFinish; DateTime m_lateFinish; QHash m_schedules; Schedule *m_currentSchedule; double m_startupCost; Account *m_startupAccount; double m_shutdownCost; Account *m_shutdownAccount; Account *m_runningAccount; Documents m_documents; ResourceRequestCollection m_requests; private: void init(); bool m_blockChanged; }; //////////////////////////////// Estimate //////////////////////////////// /** * The Estimate class stores how much time (or effort) it takes to complete a Task. * The estimate which is needed to complete the task is not simply a single value but * is stored as an optimistic, a pessimistic and an expected value. * With statistical calculations using the PERT distribution, one can arrive at a more * realistic estimate than when using the expected value alone. */ class PLANKERNEL_EXPORT Estimate { public: /// Constructor explicit Estimate( Node *parent = 0 ); /// Copy constructor. Estimate (const Estimate &estimate, Node *parent = 0); /// Destructor ~Estimate(); /// Reset estimate void clear(); /// Copy values from @p estimate Estimate &operator=( const Estimate &estimate ); /// Type defines the types of estimates enum Type { Type_Effort, /// Changing amount of resources changes the task duration Type_Duration /// Changing amount of resources will not change the tasks duration }; /// Return the node that owns this Estimate Node *parentNode() const { return m_parent; } /// Set the node that owns this Estimate void setParentNode( Node* parent ) { m_parent = parent; } /// Return estimate Type Type type() const { return m_type; } /// Set estimate type to @p type void setType(Type type); /// Set estimate type to type represented by the string @p type void setType(const QString& type); /// Return estimate type as a string. If @p trans is true, it's translated QString typeToString( bool trans=false ) const; /// Return estimate type @p typ as a string. If @p trans is true, it's translated static QString typeToString( Estimate::Type typ, bool trans=false ); /// Return a stringlist of all estimate types. Translated if @p trans = true. static QStringList typeToStringList( bool trans=false ); /// Return the calendar used when Type is Duration Calendar *calendar() const { return m_calendar; } /// Set the calendar to be used when Type is Duration void setCalendar( Calendar *calendar ); enum Risktype { Risk_None, Risk_Low, Risk_High }; Risktype risktype() const { return m_risktype; } void setRisktype(Risktype type); void setRisktype(const QString& type); QString risktypeToString( bool trans=false ) const; static QStringList risktypeToStringList( bool trans=false ); /// Use defines which value to access enum Use { Use_Expected=0, Use_Optimistic=1, Use_Pessimistic=2 }; /// Return estimate (scaled) of type @p valueType. /// If @p pert is true the pert value is calculated and returned Duration value(int valueType, bool pert) const; /// Return unscaled value Duration optimisticValue() const; /// Return unscaled value Duration pessimisticValue() const; /// Return unscaled value Duration expectedValue() const; /// The unit in which the estimates were entered. Duration::Unit unit() const { return m_unit; } /// Set display unit. void setUnit( Duration::Unit unit ); /// Return the expected estimate (normally entered by user) double expectedEstimate() const { return m_expectedEstimate; } /// Return the optimistic estimate (normally entered by user) double optimisticEstimate() const { return m_optimisticEstimate; } /// Return the pessimistic estimate (normally entered by user) double pessimisticEstimate() const { return m_pessimisticEstimate; } /// Set the expected estimate void setExpectedEstimate( double value ); /// Set the optimistic estimate void setOptimisticEstimate( double value ); /// Set the pessimistic estimate void setPessimisticEstimate( double value ); /** * Set the optimistic estimate as a deviation from "expected" in percent * @param percent should be a negative value. */ void setOptimisticRatio(int percent); /** * Return the "optimistic" estimate as deviation from "expected" in percent. * This should be a negative value. */ int optimisticRatio() const; /** * Set the pessimistic estimate as a deviation from "expected" in percent * @param percent should be a positive value. */ void setPessimisticRatio(int percent); /** * Return the "pessimistic" estimate as the deviation from "expected" in percent. * This should be a positive value. */ int pessimisticRatio() const; /** * The variance is calculated based on * the optimistic/pessimistic estimates, scaled to current unit. */ double variance() const; /** * The variance is calculated based on * the optimistic/pessimistic estimates, scaled to @p unit */ double variance( Duration::Unit unit ) const; /** * The standard deviation is calculated based on * the optimistic/pessimistic estimates, scaled to current unit. */ double deviation() const; /** * The standard deviation is calculated based on * the optimistic/pessimistic estimates, scaled to @p unit */ double deviation( Duration::Unit unit ) const; /// Returns the expected duration. Calculated based on the estimates expected, optimistic and pessimistic Duration pertExpected() const; /// Returns the most optimistic duration. Calculated based on the estimates expected, optimistic and pessimistic Duration pertOptimistic() const; /// Returns the most pessimistic duration. Calculated based on the estimates expected, optimistic and pessimistic Duration pertPessimistic() const; /// Convert the duration @p value (in milliseconds) to a value in @p unit, using the scaling factors in @p scales static double scale( const Duration &value, Duration::Unit unit, const QList &scales ); /// Convert the duration @p value (in @p unit) to a value in milliseconds (base unit), using the scaling factors in @p scales static Duration scale( double value, Duration::Unit unit, const QList &scales ); /// Return a list of default scales scaling factors static QList defaultScales(); /// Return a list of scaling factors fetched from the projects standard worktime QList scales() const; /// Load from xml document bool load(KoXmlElement &element, XMLLoaderObject &status); /// Save to xml document void save(QDomElement &element) const; protected: /// Set (calculate) cached value void setOptimisticValue(); /// Set (calculate) cached value void setExpectedValue(); /// Set (calculate) cached value void setPessimisticValue(); /// Notify parent of changes void changed() { if ( m_parent ) m_parent->changed(); } /// Copy @p estimate, parentNode is not copied void copy( const Estimate &estimate ); private: friend class Node; Node *m_parent; /// Holds the unit entered by user Duration::Unit m_unit; /// Holds the value entered by user, in unit m_unit double m_expectedEstimate; /// Holds the value entered by user, in unit m_unit double m_optimisticEstimate; /// Holds the value entered by user, in unit m_unit double m_pessimisticEstimate; mutable bool m_expectedCached, m_optimisticCached, m_pessimisticCached, m_pertCached; /// Cached value in base unit (milliseconds) Duration m_expectedValue; /// Cached value in base unit (milliseconds) Duration m_optimisticValue; /// Cached value in base unit (milliseconds) Duration m_pessimisticValue; /// Cached pert expected value mutable Duration m_pertExpected; Type m_type; Risktype m_risktype; /// Calendar may be used when Type is Type_Duration Calendar *m_calendar; }; } //KPlato namespace #endif diff --git a/src/libs/kernel/kptproject.cpp b/src/libs/kernel/kptproject.cpp index be19347b..68c97073 100644 --- a/src/libs/kernel/kptproject.cpp +++ b/src/libs/kernel/kptproject.cpp @@ -1,2944 +1,2947 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2004 - 2010, 2012 Dag Andersen Copyright (C) 2007 Florian Piquemal Copyright (C) 2007 Alexis Ménard This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptproject.h" #include "kptlocale.h" #include "kptappointment.h" #include "kpttask.h" #include "kptdatetime.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptwbsdefinition.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include "kptschedulerplugin.h" #include "kptdebug.h" #include #include #include #include #include #include namespace KPlato { Project::Project( Node *parent ) : Node( parent ), m_accounts( *this ), m_defaultCalendar( 0 ), m_config( &emptyConfig ), m_schedulerPlugins(), m_useSharedResources(false), m_sharedResourcesLoaded(false), m_loadProjectsAtStartup(false) { //debugPlan<<"("<setDefaultValues(*this); } Project::Project( ConfigBase &config, bool useDefaultValues, Node *parent ) : Node( parent ), m_accounts( *this ), m_defaultCalendar( 0 ), m_config( &config ), m_schedulerPlugins(), m_useSharedResources(false), m_sharedResourcesLoaded(false), m_loadProjectsAtStartup(false) { debugPlan<<"("<setDefaultValues(*this); } } void Project::init() { m_refCount = 1; // always used by creator m_constraint = Node::MustStartOn; m_standardWorktime = new StandardWorktime(); m_timeZone = QTimeZone::systemTimeZone(); // local timezone as default //debugPlan<= 0 ); if ( m_refCount <= 0 ) { emit aboutToBeDeleted(); deleteLater(); } } Project::~Project() { debugPlan<<"("<blockChanged(); } for (Resource *r : qAsConst(resourceIdDict)) { r->blockChanged(); } for (ResourceGroup *g : qAsConst(resourceGroupIdDict)) { g->blockChanged(); } delete m_standardWorktime; while ( !m_resourceGroups.isEmpty() ) delete m_resourceGroups.takeFirst(); while ( !m_calendars.isEmpty() ) delete m_calendars.takeFirst(); while ( !m_managers.isEmpty() ) delete m_managers.takeFirst(); m_config = 0; //not mine, don't delete } int Project::type() const { return Node::Type_Project; } void Project::generateUniqueNodeIds() { foreach ( Node *n, nodeIdDict ) { debugPlan<name()<<"old"<id(); QString uid = uniqueNodeId(); nodeIdDict.remove( n->id() ); n->setId( uid ); nodeIdDict[ uid ] = n; debugPlan<name()<<"new"<id(); } } void Project::generateUniqueIds() { generateUniqueNodeIds(); foreach ( ResourceGroup *g, resourceGroupIdDict ) { if (g->isShared()) { continue; } resourceGroupIdDict.remove( g->id() ); g->setId( uniqueResourceGroupId() ); resourceGroupIdDict[ g->id() ] = g; } foreach ( Resource *r, resourceIdDict ) { if (r->isShared()) { continue; } resourceIdDict.remove( r->id() ); r->setId( uniqueResourceId() ); resourceIdDict[ r->id() ] = r; } foreach ( Calendar *c, calendarIdDict ) { if (c->isShared()) { continue; } calendarIdDict.remove( c->id() ); c->setId( uniqueCalendarId() ); calendarIdDict[ c->id() ] = c; } } void Project::calculate( Schedule *schedule, const DateTime &dt ) { if ( schedule == 0 ) { errorPlan << "Schedule == 0, cannot calculate"; return ; } m_currentSchedule = schedule; calculate( dt ); } void Project::calculate( const DateTime &dt ) { if ( m_currentSchedule == 0 ) { errorPlan << "No current schedule to calculate"; return ; } stopcalculation = false; QLocale locale; DateTime time = dt.isValid() ? dt : DateTime( QDateTime::currentDateTime() ); MainSchedule *cs = static_cast( m_currentSchedule ); Estimate::Use estType = ( Estimate::Use ) cs->type(); if ( type() == Type_Project ) { cs->setPhaseName( 0, i18n( "Init" ) ); cs->logInfo( i18n( "Schedule project from: %1", locale.toString(dt, QLocale::ShortFormat) ), 0 ); initiateCalculation( *cs ); initiateCalculationLists( *cs ); // must be after initiateCalculation() !! propagateEarliestStart( time ); // Calculate lateFinish from time. If a task has started, remainingEffort is used. cs->setPhaseName( 1, i18nc( "Schedule project forward", "Forward" ) ); cs->logInfo( i18n( "Calculate finish" ), 1 ); cs->lateFinish = calculateForward( estType ); cs->lateFinish = checkEndConstraints( cs->lateFinish ); propagateLatestFinish( cs->lateFinish ); // Calculate earlyFinish. If a task has started, remainingEffort is used. cs->setPhaseName( 2, i18nc( "Schedule project backward","Backward" ) ); cs->logInfo( i18n( "Calculate start" ), 2 ); calculateBackward( estType ); // Schedule. If a task has started, remainingEffort is used and appointments are copied from parent cs->setPhaseName( 3, i18n( "Schedule" ) ); cs->logInfo( i18n( "Schedule tasks forward" ), 3 ); cs->endTime = scheduleForward( cs->startTime, estType ); cs->logInfo( i18n( "Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat) ), 3 ); if ( cs->endTime > m_constraintEndTime ) { cs->logError( i18n( "Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } else if ( cs->endTime == m_constraintEndTime ) { cs->logWarning( i18n( "Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } else { cs->logInfo( i18n( "Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } calcCriticalPath( false ); calcResourceOverbooked(); cs->notScheduled = false; calcFreeFloat(); emit scheduleChanged( cs ); emit projectChanged(); } else if ( type() == Type_Subproject ) { warnPlan << "Subprojects not implemented"; } else { errorPlan << "Illegal project type: " << type(); } } void Project::calculate( ScheduleManager &sm ) { emit sigCalculationStarted( this, &sm ); sm.setScheduling( true ); m_progress = 0; int nodes = 0; foreach ( Node *n, nodeIdDict ) { if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) { nodes++; } } int maxprogress = nodes * 3; if ( sm.recalculate() ) { emit maxProgress( maxprogress ); sm.setMaxProgress( maxprogress ); incProgress(); if ( sm.parentManager() ) { sm.expected()->startTime = sm.parentManager()->expected()->startTime; sm.expected()->earlyStart = sm.parentManager()->expected()->earlyStart; } incProgress(); calculate( sm.expected(), sm.recalculateFrom() ); } else { emit maxProgress( maxprogress ); sm.setMaxProgress( maxprogress ); calculate( sm.expected() ); emit scheduleChanged( sm.expected() ); setCurrentSchedule( sm.expected()->id() ); } emit sigProgress( maxprogress ); emit sigCalculationFinished( this, &sm ); emit scheduleManagerChanged( &sm ); emit projectCalculated( &sm ); emit projectChanged(); sm.setScheduling( false ); } void Project::calculate( Schedule *schedule ) { if ( schedule == 0 ) { errorPlan << "Schedule == 0, cannot calculate"; return ; } m_currentSchedule = schedule; calculate(); } void Project::calculate() { if ( m_currentSchedule == 0 ) { errorPlan << "No current schedule to calculate"; return ; } stopcalculation = false; MainSchedule *cs = static_cast( m_currentSchedule ); bool backwards = false; if ( cs->manager() ) { backwards = cs->manager()->schedulingDirection(); } QLocale locale; Estimate::Use estType = ( Estimate::Use ) cs->type(); if ( type() == Type_Project ) { QTime timer; timer.start(); initiateCalculation( *cs ); initiateCalculationLists( *cs ); // must be after initiateCalculation() !! if ( ! backwards ) { cs->setPhaseName( 0, i18n( "Init" ) ); cs->logInfo( i18n( "Schedule project forward from: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 0 ); cs->startTime = m_constraintStartTime; cs->earlyStart = m_constraintStartTime; // Calculate from start time propagateEarliestStart( cs->earlyStart ); cs->setPhaseName( 1, i18nc( "Schedule project forward", "Forward" ) ); cs->logInfo( i18n( "Calculate late finish" ), 1 ); cs->lateFinish = calculateForward( estType ); // cs->lateFinish = checkEndConstraints( cs->lateFinish ); cs->logInfo( i18n( "Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat) ), 1 ); propagateLatestFinish( cs->lateFinish ); cs->setPhaseName( 2, i18nc( "Schedule project backward", "Backward" ) ); cs->logInfo( i18n( "Calculate early start" ), 2 ); calculateBackward( estType ); cs->setPhaseName( 3, i18n( "Schedule" ) ); cs->logInfo( i18n( "Schedule tasks forward" ), 3 ); cs->endTime = scheduleForward( cs->startTime, estType ); cs->duration = cs->endTime - cs->startTime; cs->logInfo( i18n( "Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat) ), 3 ); if ( cs->endTime > m_constraintEndTime ) { cs->constraintError = true; cs->logError( i18n( "Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } else if ( cs->endTime == m_constraintEndTime ) { cs->logWarning( i18n( "Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } else { cs->logInfo( i18n( "Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 ); } calcCriticalPath( false ); } else { cs->setPhaseName( 0, i18n( "Init" ) ); cs->logInfo( i18n( "Schedule project backward from: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 0 ); // Calculate from end time propagateLatestFinish( m_constraintEndTime ); cs->setPhaseName( 1, i18nc( "Schedule project backward", "Backward" ) ); cs->logInfo( i18n( "Calculate early start" ), 1 ); cs->earlyStart = calculateBackward( estType ); // cs->earlyStart = checkStartConstraints( cs->earlyStart ); cs->logInfo( i18n( "Early start calculated: %1", locale.toString(cs->earlyStart, QLocale::ShortFormat) ), 1 ); propagateEarliestStart( cs->earlyStart ); cs->setPhaseName( 2, i18nc( "Schedule project forward", "Forward" ) ); cs->logInfo( i18n( "Calculate late finish" ), 2 ); cs->lateFinish = qMax( m_constraintEndTime, calculateForward( estType ) ); cs->logInfo( i18n( "Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat) ), 2 ); cs->setPhaseName( 3, i18n( "Schedule" ) ); cs->logInfo( i18n( "Schedule tasks backward" ), 3 ); cs->startTime = scheduleBackward( cs->lateFinish, estType ); cs->endTime = cs->startTime; foreach ( Node *n, allNodes() ) { if ( n->type() == Type_Task || n->type() == Type_Milestone ) { DateTime e = n->endTime( cs->id() ); if ( cs->endTime < e ) { cs->endTime = e; } } } if ( cs->endTime > m_constraintEndTime ) { cs->constraintError = true; cs->logError( i18n( "Failed to finish project within target time" ), 3 ); } cs->duration = cs->endTime - cs->startTime; cs->logInfo( i18n( "Scheduled start: %1, target time: %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 ); if ( cs->startTime < m_constraintStartTime ) { cs->constraintError = true; cs->logError( i18n( "Must start project early in order to finish in time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 ); } else if ( cs->startTime == m_constraintStartTime ) { cs->logWarning( i18n( "Start project exactly on time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 ); } else { cs->logInfo( i18n( "Can start project later than time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 ); } calcCriticalPath( true ); } cs->logInfo( i18n( "Calculation took: %1", KFormat().formatDuration( timer.elapsed() ) ) ); // TODO: fix this uncertainty, manager should *always* be available if (cs->manager()) { finishCalculation(*(cs->manager())); } } else if ( type() == Type_Subproject ) { warnPlan << "Subprojects not implemented"; } else { errorPlan << "Illegal project type: " << type(); } } void Project::finishCalculation( ScheduleManager &sm ) { MainSchedule *cs = sm.expected(); if (nodeIdDict.count() > 1) { // calculate project duration cs->startTime = m_constraintEndTime; cs->endTime = m_constraintStartTime; for (const Node *n : qAsConst(nodeIdDict)) { cs->startTime = qMin(cs->startTime, n->startTime(cs->id())); cs->endTime = qMax(cs->endTime, n->endTime(cs->id())); } cs->duration = cs->endTime - cs->startTime; } calcCriticalPath( false ); calcResourceOverbooked(); cs->notScheduled = false; calcFreeFloat(); emit scheduleChanged( cs ); emit projectChanged(); debugPlan<startTime<endTime<<"-------------------------"; } void Project::setProgress( int progress, ScheduleManager *sm ) { m_progress = progress; if ( sm ) { sm->setProgress( progress ); } emit sigProgress( progress ); } void Project::setMaxProgress( int max, ScheduleManager *sm ) { if ( sm ) { sm->setMaxProgress( max ); } emitMaxProgress( max ); } void Project::incProgress() { m_progress += 1; emit sigProgress( m_progress ); } void Project::emitMaxProgress( int value ) { emit maxProgress( value ); } bool Project::calcCriticalPath( bool fromEnd ) { //debugPlan; MainSchedule *cs = static_cast( m_currentSchedule ); if ( cs == 0 ) { return false; } if ( fromEnd ) { QListIterator startnodes = cs->startNodes(); while ( startnodes.hasNext() ) { startnodes.next() ->calcCriticalPath( fromEnd ); } } else { QListIterator endnodes = cs->endNodes(); while ( endnodes.hasNext() ) { endnodes.next() ->calcCriticalPath( fromEnd ); } } calcCriticalPathList( cs ); return false; } void Project::calcCriticalPathList( MainSchedule *cs ) { //debugPlan<name(); cs->clearCriticalPathList(); foreach ( Node *n, allNodes() ) { if ( n->numDependParentNodes() == 0 && n->inCriticalPath( cs->id() ) ) { cs->addCriticalPath(); cs->addCriticalPathNode( n ); calcCriticalPathList( cs, n ); } } cs->criticalPathListCached = true; //debugPlan<<*(criticalPathList( cs->id() )); } void Project::calcCriticalPathList( MainSchedule *cs, Node *node ) { //debugPlan<name()<<", "<id(); bool newPath = false; QList lst = *( cs->currentCriticalPath() ); foreach ( Relation *r, node->dependChildNodes() ) { if ( r->child()->inCriticalPath( cs->id() ) ) { if ( newPath ) { cs->addCriticalPath( &lst ); //debugPlan<name()<<" new path"; } cs->addCriticalPathNode( r->child() ); calcCriticalPathList( cs, r->child() ); newPath = true; } } } const QList< QList > *Project::criticalPathList( long id ) { Schedule *s = schedule( id ); if ( s == 0 ) { //debugPlan<<"No schedule with id="<( s ); if ( ! ms->criticalPathListCached ) { initiateCalculationLists( *ms ); calcCriticalPathList( ms ); } return ms->criticalPathList(); } QList Project::criticalPath( long id, int index ) { Schedule *s = schedule( id ); if ( s == 0 ) { //debugPlan<<"No schedule with id="<(); } MainSchedule *ms = static_cast( s ); if ( ! ms->criticalPathListCached ) { initiateCalculationLists( *ms ); calcCriticalPathList( ms ); } return ms->criticalPath( index ); } DateTime Project::startTime( long id ) const { Schedule *s = schedule( id ); return s ? s->startTime : m_constraintStartTime; } DateTime Project::endTime( long id ) const { Schedule *s = schedule( id ); return s ? s->endTime : m_constraintEndTime; } Duration Project::duration( long id ) const { Schedule *s = schedule( id ); return s ? s->duration : Duration::zeroDuration; } Duration *Project::getRandomDuration() { return 0L; } DateTime Project::checkStartConstraints( const DateTime &dt ) const { DateTime t = dt; foreach ( Node *n, nodeIdDict ) { if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) { switch ( n->constraint() ) { case Node::FixedInterval: case Node::StartNotEarlier: case Node::MustStartOn: t = qMin( t, qMax( n->constraintStartTime(), m_constraintStartTime ) ); break; default: break; } } } return t; } DateTime Project::checkEndConstraints( const DateTime &dt ) const { DateTime t = dt; foreach ( Node *n, nodeIdDict ) { if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) { switch ( n->constraint() ) { case Node::FixedInterval: case Node::FinishNotLater: case Node::MustFinishOn: t = qMax( t, qMin( n->constraintEndTime(), m_constraintEndTime ) ); break; default: break; } } } return t; } #ifndef PLAN_NLOGDEBUG bool Project::checkParent( Node *n, const QList &list, QList &checked ) { if ( n->isStartNode() ) { debugPlan< lst = list; lst << n; foreach ( Relation *r, n->dependParentNodes() ) { if ( checked.contains( r ) ) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; if ( ! checkParent( r->parent(), lst, checked ) ) { return false; } } Task *t = static_cast( n ); foreach ( Relation *r, t->parentProxyRelations() ) { if ( checked.contains( r ) ) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; debugPlan<<"Proxy:"<parent()<<":"<parent(), lst, checked ) ) { return false; } } return true; } bool Project::checkChildren( Node *n, const QList &list, QList &checked ) { if ( n->isEndNode() ) { debugPlan< lst = list; lst << n; foreach ( Relation *r, n->dependChildNodes() ) { if ( checked.contains( r ) ) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; if ( ! checkChildren( r->child(), lst, checked ) ) { return false; } } Task *t = static_cast( n ); foreach ( Relation *r, t->childProxyRelations() ) { if ( checked.contains( r ) ) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } debugPlan<<"Proxy:"<parent()<<":"<child(), lst, checked ) ) { return false; } } return true; } #endif void Project::tasksForward() { m_hardConstraints.clear(); m_softConstraints.clear(); m_terminalNodes.clear(); foreach ( Task *t, allTasks() ) { switch ( t->constraint() ) { case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: m_hardConstraints.append( t ); break; case Node::StartNotEarlier: case Node::FinishNotLater: m_softConstraints.append( t ); break; default: if ( t->isEndNode() ) { m_terminalNodes.append( t ); } break; } } #ifndef PLAN_NLOGDEBUG debugPlan<<"End nodes:"< lst; QList rel; Q_ASSERT( checkParent( n, lst, rel ) ); Q_UNUSED( n ); } #endif } void Project::tasksBackward() { m_hardConstraints.clear(); m_softConstraints.clear(); m_terminalNodes.clear(); foreach ( Task *t, allTasks() ) { switch ( t->constraint() ) { case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: m_hardConstraints.append( t ); break; case Node::StartNotEarlier: case Node::FinishNotLater: m_softConstraints.append( t ); break; default: if ( t->isStartNode() ) { m_terminalNodes.append( t ); } break; } } #ifndef PLAN_NLOGDEBUG debugPlan<<"Start nodes:"< lst; QList rel; Q_ASSERT( checkChildren( n, lst, rel ) ); Q_UNUSED( n ); } #endif } DateTime Project::calculateForward( int use ) { //debugPlan<( m_currentSchedule ); if ( cs == 0 ) { return finish; } if ( type() == Node::Type_Project ) { QTime timer; timer.start(); cs->logInfo( i18n( "Start calculating forward" ) ); m_visitedForward = true; if ( ! m_visitedBackward ) { // setup tasks tasksForward(); // Do all hard constrained first foreach ( Node *n, m_hardConstraints ) { cs->logDebug( "Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateEarlyFinish( use ); // do not do predeccessors if ( time > finish ) { finish = time; } } // do the predeccessors foreach ( Node *n, m_hardConstraints ) { cs->logDebug( "Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateForward( use ); if ( time > finish ) { finish = time; } } // now try to schedule soft constrained *with* predeccessors foreach ( Node *n, m_softConstraints ) { cs->logDebug( "Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateForward( use ); if ( time > finish ) { finish = time; } } // and then the rest using the end nodes to calculate everything (remaining) foreach ( Task *n, m_terminalNodes ) { cs->logDebug( "Calculate using end task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateForward( use ); if ( time > finish ) { finish = time; } } } else { // tasks have been calculated backwards in this order foreach ( Node *n, cs->backwardNodes() ) { DateTime time = n->calculateForward( use ); if ( time > finish ) { finish = time; } } } cs->logInfo( i18n( "Finished calculating forward: %1 ms", timer.elapsed() ) ); } else { //TODO: subproject } return finish; } DateTime Project::calculateBackward( int use ) { //debugPlan<( m_currentSchedule ); if ( cs == 0 ) { return start; } if ( type() == Node::Type_Project ) { QTime timer; timer.start(); cs->logInfo( i18n( "Start calculating backward" ) ); m_visitedBackward = true; if ( ! m_visitedForward ) { // setup tasks tasksBackward(); // Do all hard constrained first foreach ( Task *n, m_hardConstraints ) { cs->logDebug( "Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateLateStart( use ); // do not do predeccessors if ( ! start.isValid() || time < start ) { start = time; } } // then do the predeccessors foreach ( Task *n, m_hardConstraints ) { cs->logDebug( "Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateBackward( use ); if ( ! start.isValid() || time < start ) { start = time; } } // now try to schedule soft constrained *with* predeccessors foreach ( Task *n, m_softConstraints ) { cs->logDebug( "Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateBackward( use ); if ( ! start.isValid() || time < start ) { start = time; } } // and then the rest using the start nodes to calculate everything (remaining) foreach ( Task *n, m_terminalNodes ) { cs->logDebug( "Calculate using start task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->calculateBackward( use ); if ( ! start.isValid() || time < start ) { start = time; } } } else { // tasks have been calculated forwards in this order foreach ( Node *n, cs->forwardNodes() ) { DateTime time = n->calculateBackward( use ); if ( ! start.isValid() || time < start ) { start = time; } } } cs->logInfo( i18n( "Finished calculating backward: %1 ms", timer.elapsed() ) ); } else { //TODO: subproject } return start; } DateTime Project::scheduleForward( const DateTime &earliest, int use ) { DateTime end; MainSchedule *cs = static_cast( m_currentSchedule ); if ( cs == 0 || stopcalculation ) { return DateTime(); } QTime timer; timer.start(); cs->logInfo( i18n( "Start scheduling forward" ) ); resetVisited(); // Schedule in the same order as calculated forward // Do all hard constrained first foreach ( Node *n, m_hardConstraints ) { cs->logDebug( "Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->scheduleFromStartTime( use ); // do not do predeccessors if ( time > end ) { end = time; } } foreach ( Node *n, cs->forwardNodes() ) { cs->logDebug( "Schedule task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->scheduleForward( earliest, use ); if ( time > end ) { end = time; } } // Fix summarytasks adjustSummarytask(); cs->logInfo( i18n( "Finished scheduling forward: %1 ms", timer.elapsed() ) ); foreach ( Node *n, allNodes() ) { if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) { Q_ASSERT( n->isScheduled() ); } } return end; } DateTime Project::scheduleBackward( const DateTime &latest, int use ) { DateTime start; MainSchedule *cs = static_cast( m_currentSchedule ); if ( cs == 0 || stopcalculation ) { return start; } QTime timer; timer.start(); cs->logInfo( i18n( "Start scheduling backward" ) ); resetVisited(); // Schedule in the same order as calculated backward // Do all hard constrained first foreach ( Node *n, m_hardConstraints ) { cs->logDebug( "Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->scheduleFromEndTime( use ); // do not do predeccessors if ( ! start.isValid() || time < start ) { start = time; } } foreach ( Node *n, cs->backwardNodes() ) { cs->logDebug( "Schedule task:" + n->name() + " : " + n->constraintToString() ); DateTime time = n->scheduleBackward( latest, use ); if ( ! start.isValid() || time < start ) { start = time; } } // Fix summarytasks adjustSummarytask(); cs->logInfo( i18n( "Finished scheduling backward: %1 ms", timer.elapsed() ) ); foreach ( Node *n, allNodes() ) { if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) { Q_ASSERT( n->isScheduled() ); } } return start; } void Project::adjustSummarytask() { MainSchedule *cs = static_cast( m_currentSchedule ); if ( cs == 0 || stopcalculation ) { return; } QListIterator it( cs->summaryTasks() ); while ( it.hasNext() ) { it.next() ->adjustSummarytask(); } } void Project::initiateCalculation( MainSchedule &sch ) { //debugPlan< git( m_resourceGroups ); while ( git.hasNext() ) { git.next() ->initiateCalculation( sch ); } Node::initiateCalculation( sch ); } void Project::initiateCalculationLists( MainSchedule &sch ) { //debugPlan< it = childNodeIterator(); while ( it.hasNext() ) { it.next() ->initiateCalculationLists( sch ); } } else { //TODO: subproject } } bool Project::load( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan<<"--->"; m_useSharedResources = false; // default should off in case old project // load locale first KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "locale" ) { Locale *l = locale(); l->setCurrencySymbol(e.attribute( "currency-symbol", "")); if ( e.hasAttribute( "currency-digits" ) ) { l->setMonetaryDecimalPlaces(e.attribute("currency-digits").toInt()); } QLocale::Language language = QLocale::AnyLanguage; QLocale::Country country = QLocale::AnyCountry; if (e.hasAttribute("language")) { language = static_cast(e.attribute("language").toInt()); } if (e.hasAttribute("country")) { country = static_cast(e.attribute("country").toInt()); } l->setCurrencyLocale(language, country); } else if (e.tagName() == "shared-resources") { m_useSharedResources = e.attribute("use", "0").toInt(); m_sharedResourcesFile = e.attribute("file"); m_sharedProjectsUrl = QUrl(e.attribute("projects-url")); m_loadProjectsAtStartup = (bool)e.attribute("projects-loadatstartup", "0").toInt(); } } QList cals; QString s; bool ok = false; setName( element.attribute( "name" ) ); removeId( m_id ); m_id = element.attribute( "id" ); registerNodeId( this ); m_leader = element.attribute( "leader" ); m_description = element.attribute( "description" ); QTimeZone tz( element.attribute( "timezone" ).toLatin1() ); if ( tz.isValid() ) { m_timeZone = tz; } else warnPlan<<"No timezone specified, using default (local)"; status.setProjectTimeZone( m_timeZone ); // Allow for both numeric and text s = element.attribute( "scheduling", "0" ); m_constraint = ( Node::ConstraintType ) s.toInt( &ok ); if ( !ok ) setConstraint( s ); if ( m_constraint != Node::MustStartOn && m_constraint != Node::MustFinishOn ) { errorPlan << "Illegal constraint: " << constraintToString(); setConstraint( Node::MustStartOn ); } s = element.attribute( "start-time" ); if ( !s.isEmpty() ) m_constraintStartTime = DateTime::fromString( s, m_timeZone ); s = element.attribute( "end-time" ); if ( !s.isEmpty() ) m_constraintEndTime = DateTime::fromString( s, m_timeZone ); status.setProgress( 10 ); // Load the project children // Do calendars first, they only reference other calendars //debugPlan<<"Calendars--->"; n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "calendar" ) { // Load the calendar. // Referenced by resources Calendar * child = new Calendar(); child->setProject( this ); if ( child->load( e, status ) ) { cals.append( child ); // temporary, reorder later } else { // TODO: Complain about this errorPlan << "Failed to load calendar"; delete child; } } else if ( e.tagName() == "standard-worktime" ) { // Load standard worktime StandardWorktime * child = new StandardWorktime(); if ( child->load( e, status ) ) { setStandardWorktime( child ); } else { errorPlan << "Failed to load standard worktime"; delete child; } } } // calendars references calendars in arbritary saved order bool added = false; do { added = false; QList lst; while ( !cals.isEmpty() ) { Calendar *c = cals.takeFirst(); c->m_blockversion = true; if ( c->parentId().isEmpty() ) { addCalendar( c, status.baseCalendar() ); // handle pre 0.6 version added = true; //debugPlan<<"added to project:"<name(); } else { Calendar *par = calendar( c->parentId() ); if ( par ) { par->m_blockversion = true; addCalendar( c, par ); added = true; //debugPlan<<"added:"<name()<<" to parent:"<name(); par->m_blockversion = false; } else { lst.append( c ); // treat later //debugPlan<<"treat later:"<name(); } } c->m_blockversion = false; } cals = lst; } while ( added ); if ( ! cals.isEmpty() ) { errorPlan<<"All calendars not saved!"; } //debugPlan<<"Calendars<---"; status.setProgress( 15 ); // Resource groups and resources, can reference calendars n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "resource-group" ) { // Load the resources // References calendars ResourceGroup * child = new ResourceGroup(); if ( child->load( e, status ) ) { addResourceGroup( child ); } else { // TODO: Complain about this delete child; } } } status.setProgress( 20 ); // The main stuff n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "project" ) { //debugPlan<<"Sub project--->"; /* // Load the subproject Project * child = new Project( this ); if ( child->load( e ) ) { if ( !addTask( child, this ) ) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; }*/ } else if ( e.tagName() == "task" ) { //debugPlan<<"Task--->"; // Load the task (and resourcerequests). // Depends on resources already loaded Task * child = new Task( this ); if ( child->load( e, status ) ) { if ( !addTask( child, this ) ) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; } } } status.setProgress( 70 ); // These go last n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { debugPlan<"; // Load accounts // References tasks if ( !m_accounts.load( e, *this ) ) { errorPlan << "Failed to load accounts"; } } else if ( e.tagName() == "relation" ) { //debugPlan<<"Relation--->"; // Load the relation // References tasks Relation * child = new Relation(); if ( !child->load( e, *this ) ) { // TODO: Complain about this errorPlan << "Failed to load relation"; delete child; } //debugPlan<<"Relation<---"; } else if ( e.tagName() == "schedules" ) { //debugPlan<<"Project schedules & task appointments--->"; // References tasks and resources KoXmlNode sn = e.firstChild(); for ( ; ! sn.isNull(); sn = sn.nextSibling() ) { if ( ! sn.isElement() ) { continue; } KoXmlElement el = sn.toElement(); //debugPlan<loadXML( el, status ) ) { if ( add ) addScheduleManager( sm ); } else { errorPlan << "Failed to load schedule manager"; delete sm; } } else { debugPlan<<"No schedule manager ?!"; } } //debugPlan<<"Node schedules<---"; } else if ( e.tagName() == "resource-teams" ) { //debugPlan<<"Resource teams--->"; // References other resources KoXmlNode tn = e.firstChild(); for ( ; ! tn.isNull(); tn = tn.nextSibling() ) { if ( ! tn.isElement() ) { continue; } KoXmlElement el = tn.toElement(); if ( el.tagName() == "team" ) { Resource *r = findResource( el.attribute( "team-id" ) ); Resource *tm = findResource( el.attribute( "member-id" ) ); if ( r == 0 || tm == 0 ) { errorPlan<<"resource-teams: cannot find resources"; continue; } if ( r == tm ) { errorPlan<<"resource-teams: a team cannot be a member of itself"; continue; } r->addTeamMemberId( tm->id() ); } else { errorPlan<<"resource-teams: unhandled tag"<currencySymbolExplicit().isEmpty()) { loc.setAttribute("currency-symbol", l->currencySymbolExplicit()); } loc.setAttribute("currency-digits", l->monetaryDecimalPlaces()); loc.setAttribute("language", l->currencyLanguage()); loc.setAttribute("country", l->currencyCountry()); QDomElement share = me.ownerDocument().createElement( "shared-resources" ); me.appendChild(share); share.setAttribute("use", m_useSharedResources); share.setAttribute("file", m_sharedResourcesFile); share.setAttribute("projects-url", QString(m_sharedProjectsUrl.toEncoded())); share.setAttribute("projects-loadatstartup", m_loadProjectsAtStartup); if (context.saveAll(this)) { m_accounts.save( me ); // save calendars foreach ( Calendar *c, calendarIdDict ) { c->save( me ); } // save standard worktime if ( m_standardWorktime ) m_standardWorktime->save( me ); // save project resources, must be after calendars QListIterator git( m_resourceGroups ); while ( git.hasNext() ) { git.next() ->save( me ); } - - // Only save parent relations - QListIterator it( m_dependParentNodes ); - while ( it.hasNext() ) { - it.next() ->save( me ); + } + // Only save parent relations + QListIterator it( m_dependParentNodes ); + while ( it.hasNext() ) { + Relation *r = it.next(); + if (context.saveNode(r->parent()) && context.saveNode(r->child())) { + r->save(me); } - + } + if (context.saveAll(this)) { for ( int i = 0; i < numChildren(); i++ ) - // Save all children - childNode(i)->save(me, context); - - // Now we can save relations assuming no tasks have relations outside the project - QListIterator nodes( m_nodes ); - while ( nodes.hasNext() ) { - nodes.next() ->saveRelations( me ); - } - + // Save all children + childNode(i)->save(me, context); + } + // Now we can save relations assuming no tasks have relations outside the project + QListIterator nodes(m_nodes); + while (nodes.hasNext()) { + nodes.next()->saveRelations(me, context); + } + if (context.saveAll(this)) { if ( !m_managers.isEmpty() ) { QDomElement el = me.ownerDocument().createElement( "schedules" ); me.appendChild( el ); foreach ( ScheduleManager *sm, m_managers ) { sm->saveXML( el ); } } // save resource teams QDomElement el = me.ownerDocument().createElement( "resource-teams" ); me.appendChild( el ); foreach ( Resource *r, resourceIdDict ) { if ( r->type() != Resource::Type_Team ) { continue; } foreach ( const QString &id, r->teamMemberIds() ) { QDomElement e = el.ownerDocument().createElement( "team" ); el.appendChild( e ); e.setAttribute( "team-id", r->id() ); e.setAttribute( "member-id", id ); } } } } void Project::saveWorkPackageXML( QDomElement &element, const Node *node, long id ) const { QDomElement me = element.ownerDocument().createElement( "project" ); element.appendChild( me ); me.setAttribute( "name", m_name ); me.setAttribute( "leader", m_leader ); me.setAttribute( "id", m_id ); me.setAttribute( "description", m_description ); me.setAttribute( "timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString() ); me.setAttribute( "scheduling", constraintToString() ); me.setAttribute( "start-time", m_constraintStartTime.toString( Qt::ISODate ) ); me.setAttribute( "end-time", m_constraintEndTime.toString( Qt::ISODate ) ); QListIterator git( m_resourceGroups ); while ( git.hasNext() ) { git.next() ->saveWorkPackageXML( me, node->assignedResources( id ) ); } if ( node == 0 ) { return; } node->saveWorkPackageXML( me, id ); foreach ( ScheduleManager *sm, m_managerIdMap ) { if ( sm->scheduleId() == id ) { QDomElement el = me.ownerDocument().createElement( "schedules" ); me.appendChild( el ); sm->saveWorkPackageXML( el, *node ); break; } } } void Project::setParentSchedule( Schedule *sch ) { QListIterator it = m_nodes; while ( it.hasNext() ) { it.next() ->setParentSchedule( sch ); } } void Project::addResourceGroup( ResourceGroup *group, int index ) { int i = index == -1 ? m_resourceGroups.count() : index; emit resourceGroupToBeAdded( group, i ); m_resourceGroups.insert( i, group ); setResourceGroupId( group ); group->setProject( this ); foreach ( Resource *r, group->resources() ) { setResourceId( r ); r->setProject( this ); } emit resourceGroupAdded( group ); emit projectChanged(); } ResourceGroup *Project::takeResourceGroup( ResourceGroup *group ) { int i = m_resourceGroups.indexOf( group ); Q_ASSERT( i != -1 ); if ( i == -1 ) { return 0; } emit resourceGroupToBeRemoved( group ); ResourceGroup *g = m_resourceGroups.takeAt( i ); Q_ASSERT( group == g ); g->setProject( 0 ); removeResourceGroupId( g->id() ); foreach ( Resource *r, g->resources() ) { r->setProject( 0 ); removeResourceId( r->id() ); } emit resourceGroupRemoved( g ); emit projectChanged(); return g; } QList &Project::resourceGroups() { return m_resourceGroups; } void Project::addResource( ResourceGroup *group, Resource *resource, int index ) { int i = index == -1 ? group->numResources() : index; emit resourceToBeAdded( group, i ); group->addResource( i, resource, 0 ); setResourceId( resource ); emit resourceAdded( resource ); emit projectChanged(); } Resource *Project::takeResource( ResourceGroup *group, Resource *resource ) { emit resourceToBeRemoved( resource ); bool result = removeResourceId( resource->id() ); Q_ASSERT( result == true ); if (!result) { warnPlan << "Could not remove resource with id" << resource->id(); } resource->removeRequests(); // not valid anymore Resource *r = group->takeResource( resource ); Q_ASSERT( resource == r ); if (resource != r) { warnPlan << "Could not take resource from group"; } emit resourceRemoved( resource ); emit projectChanged(); return r; } void Project::moveResource( ResourceGroup *group, Resource *resource ) { if ( group == resource->parentGroup() ) { return; } takeResource( resource->parentGroup(), resource ); addResource( group, resource ); return; } QMap< QString, QString > Project::externalProjects() const { QMap< QString, QString > map; foreach ( Resource *r, resourceList() ) { for( QMapIterator it( r->externalProjects() ); it.hasNext(); ) { it.next(); if ( ! map.contains( it.key() ) ) { map[ it.key() ] = it.value(); } } } return map; } bool Project::addTask( Node* task, Node* position ) { // we want to add a task at the given position. => the new node will // become next sibling right after position. if ( 0 == position ) { return addSubTask( task, this ); } //debugPlan<<"Add"<name()<<" after"<name(); // in case we want to add to the main project, we make it child element // of the root element. if ( Node::Type_Project == position->type() ) { return addSubTask( task, position ); } // find the position // we have to tell the parent that we want to delete one of its children Node* parentNode = position->parentNode(); if ( !parentNode ) { debugPlan <<"parent node not found???"; return false; } int index = parentNode->findChildNode( position ); if ( -1 == index ) { // ok, it does not exist debugPlan <<"Task not found???"; return false; } return addSubTask( task, index + 1, parentNode ); } bool Project::addSubTask( Node* task, Node* parent ) { // append task to parent return addSubTask( task, -1, parent ); } bool Project::addSubTask( Node* task, int index, Node* parent, bool emitSignal ) { // we want to add a subtask to the node "parent" at the given index. // If parent is 0, add to this Node *p = parent; if ( 0 == p ) { p = this; } if ( !registerNodeId( task ) ) { errorPlan << "Failed to register node id, can not add subtask: " << task->name(); return false; } int i = index == -1 ? p->numChildren() : index; if ( emitSignal ) emit nodeToBeAdded( p, i ); p->insertChildNode( i, task ); connect( this, &Project::standardWorktimeChanged, task, &Node::slotStandardWorktimeChanged ); if ( emitSignal ) { emit nodeAdded( task ); emit projectChanged(); if ( p != this && p->numChildren() == 1 ) { emit nodeChanged( p ); } } return true; } void Project::takeTask( Node *node, bool emitSignal ) { //debugPlan<name(); Node * parent = node->parentNode(); if ( parent == 0 ) { debugPlan <<"Node must have a parent!"; return; } removeId( node->id() ); if ( emitSignal ) emit nodeToBeRemoved( node ); disconnect( this, &Project::standardWorktimeChanged, node, &Node::slotStandardWorktimeChanged ); parent->takeChildNode( node ); if ( emitSignal ) { emit nodeRemoved( node ); emit projectChanged(); if ( parent != this && parent->type() != Node::Type_Summarytask ) { emit nodeChanged( parent ); } } } bool Project::canMoveTask( Node* node, Node *newParent ) { //debugPlan<name()<<" to"<name(); if ( node == this ) { return false; } Node *p = newParent; while ( p && p != this ) { if ( ! node->canMoveTo( p ) ) { return false; } p = p->parentNode(); } return true; } bool Project::moveTask( Node* node, Node *newParent, int newPos ) { //debugPlan<name()<<" to"<name()<<","<parentNode(); int oldPos = oldParent->indexOf( node ); int i = newPos < 0 ? newParent->numChildren() : newPos; if ( oldParent == newParent && i == oldPos ) { // no need to move to where it already is return false; } int newRow = i; if ( oldParent == newParent && newPos > oldPos ) { ++newRow; // itemmodels wants new row *before* node is removed from old position } debugPlan<name()<<"at"<indexOf( node )<<"to"<name()<numChildren() == 0 ) { emit nodeChanged( oldParent ); } if ( newParent != this && newParent->numChildren() == 1 ) { emit nodeChanged( newParent ); } return true; } bool Project::canIndentTask( Node* node ) { if ( 0 == node ) { // should always be != 0. At least we would get the Project, // but you never know who might change that, so better be careful return false; } if ( node->type() == Node::Type_Project ) { //debugPlan<<"The root node cannot be indented"; return false; } // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if ( !parentNode ) { return false; } if ( parentNode->findChildNode( node ) == -1 ) { errorPlan << "Tasknot found???"; return false; } Node *sib = node->siblingBefore(); if ( !sib ) { //debugPlan<<"new parent node not found"; return false; } if ( node->findParentRelation( sib ) || node->findChildRelation( sib ) ) { //debugPlan<<"Cannot have relations to parent"; return false; } return true; } bool Project::indentTask( Node* node, int index ) { if ( canIndentTask( node ) ) { Node * newParent = node->siblingBefore(); int i = index == -1 ? newParent->numChildren() : index; moveTask( node, newParent, i ); //debugPlan; return true; } return false; } bool Project::canUnindentTask( Node* node ) { if ( 0 == node ) { // is always != 0. At least we would get the Project, but you // never know who might change that, so better be careful return false; } if ( Node::Type_Project == node->type() ) { //debugPlan<<"The root node cannot be unindented"; return false; } // we have to find the parent of task to manipulate its list of children // and we need the parent's parent too Node* parentNode = node->parentNode(); if ( !parentNode ) { return false; } Node* grandParentNode = parentNode->parentNode(); if ( !grandParentNode ) { //debugPlan<<"This node already is at the top level"; return false; } int index = parentNode->findChildNode( node ); if ( -1 == index ) { errorPlan << "Tasknot found???"; return false; } return true; } bool Project::unindentTask( Node* node ) { if ( canUnindentTask( node ) ) { Node * parentNode = node->parentNode(); Node *grandParentNode = parentNode->parentNode(); int i = grandParentNode->indexOf( parentNode ) + 1; if ( i == 0 ) { i = grandParentNode->numChildren(); } moveTask( node, grandParentNode, i ); //debugPlan; return true; } return false; } bool Project::canMoveTaskUp( Node* node ) { if ( node == 0 ) return false; // safety // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if ( !parentNode ) { //debugPlan<<"No parent found"; return false; } if ( parentNode->findChildNode( node ) == -1 ) { errorPlan << "Tasknot found???"; return false; } if ( node->siblingBefore() ) { return true; } return false; } bool Project::moveTaskUp( Node* node ) { if ( canMoveTaskUp( node ) ) { moveTask( node, node->parentNode(), node->parentNode()->indexOf( node ) - 1 ); return true; } return false; } bool Project::canMoveTaskDown( Node* node ) { if ( node == 0 ) return false; // safety // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if ( !parentNode ) { return false; } if ( parentNode->findChildNode( node ) == -1 ) { errorPlan << "Tasknot found???"; return false; } if ( node->siblingAfter() ) { return true; } return false; } bool Project::moveTaskDown( Node* node ) { if ( canMoveTaskDown( node ) ) { moveTask( node, node->parentNode(), node->parentNode()->indexOf( node ) + 1 ); return true; } return false; } Task *Project::createTask() { Task * node = new Task(); node->setId( uniqueNodeId() ); reserveId( node->id(), node ); return node; } Task *Project::createTask( const Task &def ) { Task * node = new Task( def ); node->setId( uniqueNodeId() ); reserveId( node->id(), node ); return node; } Node *Project::findNode( const QString &id ) const { if ( m_parent == 0 ) { if ( nodeIdDict.contains( id ) ) { return nodeIdDict[ id ]; } return 0; } return m_parent->findNode( id ); } bool Project::nodeIdentExists( const QString &id ) const { return nodeIdDict.contains( id ) || nodeIdReserved.contains( id ); } QString Project::uniqueNodeId( int seed ) const { Q_UNUSED(seed); QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' '; QString ident = s + KRandom::randomString( 10 ); // int i = seed; while ( nodeIdentExists( ident ) ) { ident = s + KRandom::randomString( 10 ); } return ident; } QString Project::uniqueNodeId( const QList &existingIds, int seed ) { QString id = uniqueNodeId( seed ); while ( existingIds.contains( id ) ) { id = uniqueNodeId( seed ); } return id; } bool Project::removeId( const QString &id ) { //debugPlan <<"id=" << id; if ( m_parent ) { return m_parent->removeId( id ); } //debugPlan << "id=" << id<< nodeIdDict.contains(id); return nodeIdDict.remove( id ); } void Project::reserveId( const QString &id, Node *node ) { //debugPlan <<"id=" << id << node->name(); nodeIdReserved.insert( id, node ); } bool Project::registerNodeId( Node *node ) { nodeIdReserved.remove( node->id() ); if ( node->id().isEmpty() ) { warnPlan << "Node id is empty, cannot register it"; return false; } Node *rn = findNode( node->id() ); if ( rn == 0 ) { //debugPlan <<"id=" << node->id() << node->name(); nodeIdDict.insert( node->id(), node ); return true; } if ( rn != node ) { errorPlan << "Id already exists for different task: " << node->id(); return false; } //debugPlan<<"Already exists" <<"id=" << node->id() << node->name(); return true; } QList Project::allNodes() const { QList lst = nodeIdDict.values(); int me = lst.indexOf( const_cast( this ) ); if ( me != -1 ) { lst.removeAt( me ); } return lst; } QList Project::allTasks( const Node *parent ) const { QList lst; const Node *p = parent ? parent : this; foreach ( Node *n, p->childNodeIterator() ) { if ( n->type() == Node::Type_Task || n->type() == Type_Milestone ) { lst << static_cast( n ); } lst += allTasks( n ); } return lst; } bool Project::setResourceGroupId( ResourceGroup *group ) { if ( group == 0 ) { return false; } if ( ! group->id().isEmpty() ) { ResourceGroup *g = findResourceGroup( group->id() ); if ( group == g ) { return true; } else if ( g == 0 ) { insertResourceGroupId( group->id(), group ); return true; } } QString id = uniqueResourceGroupId(); group->setId( id ); if ( id.isEmpty() ) { return false; } insertResourceGroupId( id, group ); return true; } QString Project::uniqueResourceGroupId() const { QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' '; QString id = s + KRandom::randomString( 10 ); while ( resourceGroupIdDict.contains( id ) ) { id = s + KRandom::randomString( 10 ); } return id; } ResourceGroup *Project::group( const QString& id ) { return findResourceGroup( id ); } ResourceGroup *Project::groupByName( const QString& name ) const { foreach ( ResourceGroup *g, resourceGroupIdDict ) { if ( g->name() == name ) { return g; } } return 0; } QList Project::autoAllocateResources() const { QList lst; foreach ( Resource *r, resourceIdDict ) { if ( r->autoAllocate() ) { lst << r; } } return lst; } void Project::insertResourceId( const QString &id, Resource *resource ) { resourceIdDict.insert( id, resource ); } bool Project::removeResourceId( const QString &id ) { return resourceIdDict.remove( id ); } bool Project::setResourceId( Resource *resource ) { if ( resource == 0 ) { return false; } if ( ! resource->id().isEmpty() ) { Resource *r = findResource( resource->id() ); if ( resource == r ) { return true; } else if ( r == 0 ) { insertResourceId( resource->id(), resource ); return true; } } QString id = uniqueResourceId(); resource->setId( id ); if ( id.isEmpty() ) { return false; } insertResourceId( id, resource ); return true; } QString Project::uniqueResourceId() const { QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' '; QString id = s + KRandom::randomString( 10 ); while ( resourceIdDict.contains( id ) ) { id = s + KRandom::randomString( 10 ); } return id; } Resource *Project::resource( const QString& id ) { return findResource( id ); } Resource *Project::resourceByName( const QString& name ) const { QHash::const_iterator it; for (it = resourceIdDict.constBegin(); it != resourceIdDict.constEnd(); ++it) { Resource *r = it.value(); if ( r->name() == name ) { Q_ASSERT( it.key() == r->id() ); return r; } } return 0; } QStringList Project::resourceNameList() const { QStringList lst; foreach ( Resource *r, resourceIdDict ) { lst << r->name(); } return lst; } EffortCostMap Project::plannedEffortCostPrDay( QDate start, QDate end, long id, EffortCostCalculationType typ ) const { //debugPlan< it( childNodeIterator() ); while ( it.hasNext() ) { ec += it.next() ->plannedEffortCostPrDay( start, end, id, typ ); } return ec; } EffortCostMap Project::plannedEffortCostPrDay( const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const { //debugPlan< it( childNodeIterator() ); while ( it.hasNext() ) { ec += it.next() ->plannedEffortCostPrDay( resource, start, end, id, typ ); } return ec; } EffortCostMap Project::actualEffortCostPrDay( QDate start, QDate end, long id, EffortCostCalculationType typ ) const { //debugPlan< it( childNodeIterator() ); while ( it.hasNext() ) { ec += it.next() ->actualEffortCostPrDay( start, end, id, typ ); } return ec; } EffortCostMap Project::actualEffortCostPrDay( const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const { //debugPlan< it( childNodeIterator() ); while ( it.hasNext() ) { ec += it.next() ->actualEffortCostPrDay( resource, start, end, id, typ ); } return ec; } // Returns the total planned effort for this project (or subproject) Duration Project::plannedEffort( long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { eff += it.next() ->plannedEffort( id, typ ); } return eff; } // Returns the total planned effort for this project (or subproject) on date Duration Project::plannedEffort( QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { eff += it.next() ->plannedEffort( date, id, typ ); } return eff; } // Returns the total planned effort for this project (or subproject) upto and including date Duration Project::plannedEffortTo( QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { eff += it.next() ->plannedEffortTo( date, id, typ ); } return eff; } // Returns the total actual effort for this project (or subproject) upto and including date Duration Project::actualEffortTo( QDate date ) const { //debugPlan; Duration eff; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { eff += it.next() ->actualEffortTo( date ); } return eff; } // Returns the total planned effort for this project (or subproject) upto and including date double Project::plannedCostTo( QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; double c = 0; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { c += it.next() ->plannedCostTo( date, id, typ ); } return c; } // Returns the total actual cost for this project (or subproject) upto and including date EffortCost Project::actualCostTo( long int id, QDate date ) const { //debugPlan; EffortCost c; QListIterator it( childNodeIterator() ); while ( it.hasNext() ) { c += it.next() ->actualCostTo( id, date ); } return c; } Duration Project::budgetedWorkPerformed( QDate date, long id ) const { //debugPlan; Duration e; foreach (Node *n, childNodeIterator()) { e += n->budgetedWorkPerformed( date, id ); } return e; } double Project::budgetedCostPerformed( QDate date, long id ) const { //debugPlan; double c = 0.0; foreach (Node *n, childNodeIterator()) { c += n->budgetedCostPerformed( date, id ); } return c; } double Project::effortPerformanceIndex( QDate date, long id ) const { //debugPlan; debugPlan< 0.0 ) { r = p / s; } debugPlan< date ? end : date), id ); double budgetAtCompletion; double plannedCompleted; double budgetedCompleted; bool useEffort = false; //FIXME if ( useEffort ) { budgetAtCompletion = plan.totalEffort().toDouble( Duration::Unit_h ); plannedCompleted = plan.effortTo( date ).toDouble( Duration::Unit_h ); //actualCompleted = actual.effortTo( date ).toDouble( Duration::Unit_h ); budgetedCompleted = budgetedWorkPerformed( date, id ).toDouble( Duration::Unit_h ); } else { budgetAtCompletion = plan.totalCost(); plannedCompleted = plan.costTo( date ); budgetedCompleted = budgetedCostPerformed( date, id ); } double c = 0.0; if ( budgetAtCompletion > 0.0 ) { double percentageCompletion = budgetedCompleted / budgetAtCompletion; c = budgetAtCompletion * percentageCompletion; //?? debugPlan<name()<<","<<(parent?parent->name():"No parent"); int row = parent == 0 ? m_calendars.count() : parent->calendars().count(); if ( index >= 0 && index < row ) { row = index; } emit calendarToBeAdded( parent, row ); calendar->setProject( this ); if ( parent == 0 ) { calendar->setParentCal( 0 ); // in case m_calendars.insert( row, calendar ); } else { calendar->setParentCal( parent, row ); } if ( calendar->isDefault() ) { setDefaultCalendar( calendar ); } setCalendarId( calendar ); emit calendarAdded( calendar ); emit projectChanged(); } void Project::takeCalendar( Calendar *calendar ) { emit calendarToBeRemoved( calendar ); removeCalendarId( calendar->id() ); if ( calendar == m_defaultCalendar ) { m_defaultCalendar = 0; } if ( calendar->parentCal() == 0 ) { int i = indexOf( calendar ); if ( i != -1 ) { m_calendars.removeAt( i ); } } else { calendar->setParentCal( 0 ); } emit calendarRemoved( calendar ); calendar->setProject( 0 ); emit projectChanged(); } int Project::indexOf( const Calendar *calendar ) const { return m_calendars.indexOf( const_cast(calendar) ); } Calendar *Project::calendar( const QString& id ) const { return findCalendar( id ); } Calendar *Project::calendarByName( const QString& name ) const { foreach( Calendar *c, calendarIdDict ) { if ( c->name() == name ) { return c; } } return 0; } const QList &Project::calendars() const { return m_calendars; } QList Project::allCalendars() const { return calendarIdDict.values(); } QStringList Project::calendarNames() const { QStringList lst; foreach( Calendar *c, calendarIdDict ) { lst << c->name(); } return lst; } bool Project::setCalendarId( Calendar *calendar ) { if ( calendar == 0 ) { return false; } if ( ! calendar->id().isEmpty() ) { Calendar *c = findCalendar( calendar->id() ); if ( calendar == c ) { return true; } else if ( c == 0 ) { insertCalendarId( calendar->id(), calendar ); return true; } } QString id = uniqueCalendarId(); calendar->setId( id ); if ( id.isEmpty() ) { return false; } insertCalendarId( id, calendar ); return true; } QString Project::uniqueCalendarId() const { QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' '; QString id = s + KRandom::randomString( 10 ); while ( calendarIdDict.contains( id ) ) { id = s + KRandom::randomString( 10 ); } return id; } void Project::setDefaultCalendar( Calendar *cal ) { if ( m_defaultCalendar ) { m_defaultCalendar->setDefault( false ); } m_defaultCalendar = cal; if ( cal ) { cal->setDefault( true ); } emit defaultCalendarChanged( cal ); emit projectChanged(); } void Project::setStandardWorktime( StandardWorktime * worktime ) { if ( m_standardWorktime != worktime ) { delete m_standardWorktime; m_standardWorktime = worktime; m_standardWorktime->setProject( this ); emit standardWorktimeChanged( worktime ); } } void Project::emitDocumentAdded( Node *node , Document *doc , int index ) { emit documentAdded( node, doc, index ); } void Project::emitDocumentRemoved( Node *node , Document *doc , int index ) { emit documentRemoved( node, doc, index ); } void Project::emitDocumentChanged( Node *node , Document *doc , int index ) { emit documentChanged( node, doc, index ); } bool Project::linkExists( const Node *par, const Node *child ) const { if ( par == 0 || child == 0 || par == child || par->isDependChildOf( child ) ) { return false; } foreach ( Relation *r, par->dependChildNodes() ) { if ( r->child() == child ) { return true; } } return false; } bool Project::legalToLink( const Node *par, const Node *child ) const { //debugPlan<isDependChildOf( child ) ) { return false; } if ( linkExists( par, child ) ) { return false; } bool legal = true; // see if par/child is related if ( legal && ( par->isParentOf( child ) || child->isParentOf( par ) ) ) { legal = false; } if ( legal ) legal = legalChildren( par, child ); if ( legal ) legal = legalParents( par, child ); if ( legal ) { foreach ( Node *p, par->childNodeIterator() ) { if ( ! legalToLink( p, child ) ) { return false; } } } return legal; } bool Project::legalParents( const Node *par, const Node *child ) const { bool legal = true; //debugPlan<name()<<" ("<numDependParentNodes()<<" parents)"<name()<<" ("<numDependChildNodes()<<" children)"; for ( int i = 0; i < par->numDependParentNodes() && legal; ++i ) { Node *pNode = par->getDependParentNode( i ) ->parent(); if ( child->isParentOf( pNode ) || pNode->isParentOf( child ) ) { //debugPlan<<"Found:"<name()<<" is related to"<name(); legal = false; } else { legal = legalChildren( pNode, child ); } if ( legal ) legal = legalParents( pNode, child ); } return legal; } bool Project::legalChildren( const Node *par, const Node *child ) const { bool legal = true; //debugPlan<name()<<" ("<numDependParentNodes()<<" parents)"<name()<<" ("<numDependChildNodes()<<" children)"; for ( int j = 0; j < child->numDependChildNodes() && legal; ++j ) { Node *cNode = child->getDependChildNode( j ) ->child(); if ( par->isParentOf( cNode ) || cNode->isParentOf( par ) ) { //debugPlan<<"Found:"<name()<<" is related to"<name(); legal = false; } else { legal = legalChildren( par, cNode ); } } return legal; } WBSDefinition &Project::wbsDefinition() { return m_wbsDefinition; } void Project::setWbsDefinition( const WBSDefinition &def ) { //debugPlan; m_wbsDefinition = def; emit wbsDefinitionChanged(); emit projectChanged(); } QString Project::generateWBSCode( QList &indexes, bool sortable ) const { QString code = m_wbsDefinition.projectCode(); if (sortable) { int fw = (nodeIdDict.count() / 10) + 1; QLatin1Char fc('0'); foreach ( int index, indexes ) { code += ".%1"; code = code.arg(QString::number(index), fw, fc); } debugPlan< hash = resourceIdDict; foreach ( Resource * r, hash ) { r->setCurrentSchedule( id ); } emit currentScheduleChanged(); emit projectChanged(); } ScheduleManager *Project::scheduleManager( long id ) const { foreach ( ScheduleManager *sm, m_managers ) { if ( sm->scheduleId() == id ) { return sm; } } return 0; } ScheduleManager *Project::scheduleManager( const QString &id ) const { return m_managerIdMap.value( id ); } ScheduleManager *Project::findScheduleManagerByName( const QString &name ) const { //debugPlan; ScheduleManager *m = 0; foreach( ScheduleManager *sm, m_managers ) { m = sm->findManager( name ); if ( m ) { break; } } return m; } QList Project::allScheduleManagers() const { QList lst; foreach ( ScheduleManager *sm, m_managers ) { lst << sm; lst << sm->allChildren(); } return lst; } QString Project::uniqueScheduleName() const { //debugPlan; QString n = i18n( "Plan" ); bool unique = findScheduleManagerByName( n ) == 0; if ( unique ) { return n; } n += " %1"; int i = 1; for ( ; true; ++i ) { unique = findScheduleManagerByName( n.arg( i ) ) == 0; if ( unique ) { break; } } return n.arg( i ); } void Project::addScheduleManager( ScheduleManager *sm, ScheduleManager *parent, int index ) { int row = parent == 0 ? m_managers.count() : parent->childCount(); if ( index >= 0 && index < row ) { row = index; } if ( parent == 0 ) { emit scheduleManagerToBeAdded( parent, row ); m_managers.insert( row, sm ); } else { emit scheduleManagerToBeAdded( parent, row ); sm->setParentManager( parent, row ); } if ( sm->managerId().isEmpty() ) { sm->setManagerId( uniqueScheduleManagerId() ); } Q_ASSERT( ! m_managerIdMap.contains( sm->managerId() ) ); m_managerIdMap.insert( sm->managerId(), sm ); emit scheduleManagerAdded( sm ); emit projectChanged(); //debugPlan<<"Added:"<name()<<", now"<children() ) { takeScheduleManager( s ); } if ( sm->scheduling() ) { sm->stopCalculation(); } int index = -1; if ( sm->parentManager() ) { int index = sm->parentManager()->indexOf( sm ); if ( index >= 0 ) { emit scheduleManagerToBeRemoved( sm ); sm->setParentManager( 0 ); m_managerIdMap.remove( sm->managerId() ); emit scheduleManagerRemoved( sm ); emit projectChanged(); } } else { index = indexOf( sm ); if ( index >= 0 ) { emit scheduleManagerToBeRemoved( sm ); m_managers.removeAt( indexOf( sm ) ); m_managerIdMap.remove( sm->managerId() ); emit scheduleManagerRemoved( sm ); emit projectChanged(); } } return index; } void Project::moveScheduleManager( ScheduleManager *sm, ScheduleManager *newparent, int newindex ) { //debugPlan<name()<parentManager() ) { m_managers.removeAt( indexOf( sm ) ); } sm->setParentManager( newparent, newindex ); if ( ! newparent ) { m_managers.insert( newindex, sm ); } emit scheduleManagerMoved( sm, newindex ); } bool Project::isScheduleManager( void *ptr ) const { const ScheduleManager *sm = static_cast( ptr ); if ( indexOf( sm ) >= 0 ) { return true; } foreach ( ScheduleManager *p, m_managers ) { if ( p->isParentOf( sm ) ) { return true; } } return false; } ScheduleManager *Project::createScheduleManager( const QString &name ) { //debugPlan<isBaselined() ) { return true; } } return false; } Schedule *s = schedule( id ); return s == 0 ? false : s->isBaselined(); } MainSchedule *Project::createSchedule( const QString& name, Schedule::Type type ) { //debugPlan<<"No of schedules:"<setName( name ); sch->setType( type ); addMainSchedule( sch ); return sch; } void Project::addMainSchedule( MainSchedule *sch ) { if ( sch == 0 ) { return; } //debugPlan<<"No of schedules:"<setId( i ); sch->setNode( this ); addSchedule( sch ); } bool Project::removeCalendarId( const QString &id ) { //debugPlan <<"id=" << id; return calendarIdDict.remove( id ); } void Project::insertCalendarId( const QString &id, Calendar *calendar ) { //debugPlan <<"id=" << id <<":" << calendar->name(); calendarIdDict.insert( id, calendar ); } void Project::changed( Node *node, int property ) { if ( m_parent == 0 ) { Node::changed( node, property ); // reset cache if ( property != Node::Type ) { // add/remove node is handled elsewhere emit nodeChanged( node ); emit projectChanged(); } return; } Node::changed( node, property ); } void Project::changed( ResourceGroup *group ) { //debugPlan; emit resourceGroupChanged( group ); emit projectChanged(); } void Project::changed( ScheduleManager *sm ) { emit scheduleManagerChanged( sm ); emit projectChanged(); } void Project::changed( MainSchedule *sch ) { //debugPlan<id(); emit scheduleChanged( sch ); emit projectChanged(); } void Project::sendScheduleToBeAdded( const ScheduleManager *sm, int row ) { emit scheduleToBeAdded( sm, row ); } void Project::sendScheduleAdded( const MainSchedule *sch ) { //debugPlan<id(); emit scheduleAdded( sch ); emit projectChanged(); } void Project::sendScheduleToBeRemoved( const MainSchedule *sch ) { //debugPlan<id(); emit scheduleToBeRemoved( sch ); } void Project::sendScheduleRemoved( const MainSchedule *sch ) { //debugPlan<id(); emit scheduleRemoved( sch ); emit projectChanged(); } void Project::changed( Resource *resource ) { emit resourceChanged( resource ); emit projectChanged(); } void Project::changed( Calendar *cal ) { emit calendarChanged( cal ); emit projectChanged(); } void Project::changed( StandardWorktime *w ) { emit standardWorktimeChanged( w ); emit projectChanged(); } bool Project::addRelation( Relation *rel, bool check ) { if ( rel->parent() == 0 || rel->child() == 0 ) { return false; } if ( check && !legalToLink( rel->parent(), rel->child() ) ) { return false; } emit relationToBeAdded( rel, rel->parent()->numDependChildNodes(), rel->child()->numDependParentNodes() ); rel->parent()->addDependChildNode( rel ); rel->child()->addDependParentNode( rel ); emit relationAdded( rel ); emit projectChanged(); return true; } void Project::takeRelation( Relation *rel ) { emit relationToBeRemoved( rel ); rel->parent() ->takeDependChildNode( rel ); rel->child() ->takeDependParentNode( rel ); emit relationRemoved( rel ); emit projectChanged(); } void Project::setRelationType( Relation *rel, Relation::Type type ) { emit relationToBeModified( rel ); rel->setType( type ); emit relationModified( rel ); emit projectChanged(); } void Project::setRelationLag( Relation *rel, const Duration &lag ) { emit relationToBeModified( rel ); rel->setLag( lag ); emit relationModified( rel ); emit projectChanged(); } QList Project::flatNodeList( Node *parent ) { QList lst; Node *p = parent == 0 ? this : parent; //debugPlan<name()<childNodeIterator() ) { lst.append( n ); if ( n->numChildren() > 0 ) { lst += flatNodeList( n ); } } return lst; } void Project::setSchedulerPlugins( const QMap &plugins ) { m_schedulerPlugins = plugins; debugPlan< * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "InsertProjectXmlCommand.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptcalendar.h" #include "kptrelation.h" #include "kptresource.h" #include "kptdocuments.h" #include "kptlocale.h" #include "kptdebug.h" #include const QLoggingCategory &PLANCMDINSPROJECT_LOG() { static const QLoggingCategory category("calligra.plan.command.insertProjectXml"); return category; } #define debugPlanInsertProjectXml qCDebug(PLANCMDINSPROJECT_LOG) #define warnPlanInsertProjectXml qCWarning(PLANCMDINSPROJECT_LOG) #define errorPlanInsertProjectXml qCCritical(PLANCMDINSPROJECT_LOG) using namespace KPlato; AddTaskCommand::AddTaskCommand(Project *project, Node *parent, Node *node, Node *after, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_parent(parent) , m_node(node) , m_after(after) , m_added(false) { } AddTaskCommand::~AddTaskCommand() { - qInfo()<name()<indexOf(m_node); m_project->addSubTask(m_node, m_parent->indexOf(m_node), m_parent, true); m_added = true; } void AddTaskCommand::unexecute() { m_project->takeTask(m_node); m_added = false; } InsertProjectXmlCommand::InsertProjectXmlCommand(Project *project, const QByteArray &data, Node *parent, Node *after, const KUndo2MagicString& name) : MacroCommand(name) , m_project(project) , m_data(data) , m_parent(parent) , m_after(after) , m_first(true) { //debugPlan<name(); Q_ASSERT(project != 0); m_context.setProject(project); m_context.setProjectTimeZone(project->timeZone()); // from xml doc? m_context.setLoadTaskChildren(false); } InsertProjectXmlCommand::~InsertProjectXmlCommand() { } void InsertProjectXmlCommand::execute() { if (m_first) { // create and execute commands KoXmlDocument doc; doc.setContent(m_data); KoXmlElement projectElement = doc.documentElement().namedItem("project").toElement(); createCmdAccounts(projectElement); createCmdCalendars(projectElement); createCmdResources(projectElement); createCmdTasks(projectElement); createCmdRelations(projectElement); m_first = false; m_data.clear(); } else { MacroCommand::execute(); } } void InsertProjectXmlCommand::unexecute() { MacroCommand::unexecute(); } void InsertProjectXmlCommand::createCmdAccounts(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertProjectXmlCommand::createCmdCalendars(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertProjectXmlCommand::createCmdResources(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertProjectXmlCommand::createCmdTasks(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } createCmdTask(projectElement, m_project, m_after); } void InsertProjectXmlCommand::createCmdTask(const KoXmlElement &parentElement, Node *parent, Node *after) { KoXmlElement taskElement; forEachElement(taskElement, parentElement) { if (taskElement.tagName() != "task") { continue; } Task *task = m_project->createTask(); QString id = task->id(); - m_oldIds.insert(id, task); task->load(taskElement, m_context); + m_oldIds.insert(task->id(), task); task->setId(id); NamedCommand *cmd = new AddTaskCommand(m_project, parent, task, after); cmd->execute(); addCommand(cmd); -// createCmdRequests(taskElement, task); createCmdTask(taskElement, task); // add children } } void InsertProjectXmlCommand::createCmdRelations(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } KoXmlElement relationElement; forEachElement(relationElement, projectElement) { if (relationElement.tagName() != "relation") { continue; } - Node *parent = m_oldIds.value(relationElement.attribute("parent")); - Node *child = m_oldIds.value(relationElement.attribute("child")); + Node *parent = m_oldIds.value(relationElement.attribute("parent-id")); + Node *child = m_oldIds.value(relationElement.attribute("child-id")); if (parent && child) { Relation *relation = new Relation(parent, child); relation->setType(relationElement.attribute("type")); relation->setLag(Duration::fromString(relationElement.attribute("lag"))); AddRelationCmd *cmd = new AddRelationCmd(*m_project, relation); cmd->execute(); addCommand(cmd); } } } diff --git a/src/libs/models/tests/InsertProjectXmlCommandTester.cpp b/src/libs/models/tests/InsertProjectXmlCommandTester.cpp index 6b10e8d4..9318fe3b 100644 --- a/src/libs/models/tests/InsertProjectXmlCommandTester.cpp +++ b/src/libs/models/tests/InsertProjectXmlCommandTester.cpp @@ -1,188 +1,224 @@ /* This file is part of the KDE project Copyright (C) 2019 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "InsertProjectXmlCommandTester.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include "kptappointment.h" #include "XmlSaveContext.h" #include "InsertProjectXmlCommand.h" #include #include namespace QTest { template<> char *toString(const KPlato::DateTime &dt) { return toString(dt.toString()); } } using namespace KPlato; void InsertProjectXmlCommandTester::printDebug(Project *project) const { Project *p = project; qInfo()<<"Debug info -------------------------------------"; qInfo()<<"Project:"<allTasks().count(); for (int i = 0; i < p->numChildren(); ++i) { qInfo()<childNode(i); const ResourceRequestCollection &coll = p->childNode(i)->requests(); qInfo()<<"\tRequests:"<childNode(i)->requests().requests().at(g); qInfo()<<"\tGroup:"<group(); for (int r = 0; r < gr->resourceRequests().count(); ++r) { ResourceRequest *rr = gr->resourceRequests().at(r); qInfo()<<"\t\tResource:"<resource(); } } } } Project *createProject() { Project *project = new Project(); project->setName("P1"); project->setId(project->uniqueNodeId()); project->registerNodeId(project); DateTime targetstart = DateTime(QDate::currentDate(), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(3)); project->setConstraintStartTime(targetstart); project->setConstraintEndTime(targetend); - + // standard worktime defines 8 hour day as default Calendar *calendar = new Calendar("Test"); calendar->setDefault(true); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); for (int i=1; i <= 7; ++i) { CalendarDay *d = calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } project->addCalendar(calendar); return project; } void addResources(Project *project) { ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project->addResourceGroup(g); Resource *resource = new Resource(); resource->setName("R1"); resource->setCalendar(project->calendars().value(0)); project->addResource(g, resource); } void addTask(Project *project, const QString &name, Task *parent = 0) { Task *task = project->createTask(); task->setName(name); project->addSubTask(task, parent ? (Node*)parent : (Node*)project); task->estimate()->setUnit(Duration::Unit_h); task->estimate()->setExpectedEstimate(8.0); task->estimate()->setType(Estimate::Type_Effort); } + void addRequests(Node *task, ResourceGroup *g, Resource *r) { ResourceGroupRequest *gr = new ResourceGroupRequest(g); gr->addResourceRequest(new ResourceRequest(r, 100)); task->requests().addRequest(gr); } + +void addDependency(Node *t1, Node *t2) +{ + t1->addDependChildNode(t2, Relation::FinishStart); +} + void InsertProjectXmlCommandTester::init() { m_project = createProject(); } void InsertProjectXmlCommandTester::cleanup() { printDebug(m_project); delete m_project; } void InsertProjectXmlCommandTester::copyBasics() { addTask(m_project, "T1"); XmlSaveContext context(m_project); context.options = XmlSaveContext::SaveNodes; context.nodes << m_project->childNode(0); context.save(); qInfo()<childNode(0)); cmd.redo(); QCOMPARE(m_project->allTasks().count(), 2); } void InsertProjectXmlCommandTester::copyRequests() { addResources(m_project); - + addTask(m_project, "T1"); addTask(m_project, "T2"); - + addRequests(m_project->childNode(0), m_project->resourceGroupAt(0), m_project->resourceGroupAt(0)->resourceAt(0)); addRequests(m_project->childNode(1), m_project->resourceGroupAt(0), m_project->resourceGroupAt(0)->resourceAt(0)); printDebug(m_project); XmlSaveContext context(m_project); context.options = XmlSaveContext::SaveNodes; context.nodes << m_project->childNode(0) << m_project->childNode(1); context.save(); qInfo()<childNode(1)); cmd.redo(); printDebug(m_project); QCOMPARE(m_project->allTasks().count(), 4); Node *copy1 = m_project->childNode(2); QVERIFY(m_project->childNode(0)->id() != copy1->id()); QCOMPARE(m_project->childNode(0)->name(), copy1->name()); QCOMPARE(m_project->childNode(0)->requests().requests().count(), copy1->requests().requests().count()); ResourceGroupRequest *gr = m_project->childNode(0)->requests().requests().at(0); ResourceGroupRequest *cpgr = copy1->requests().requests().at(0); QCOMPARE(gr->group(), cpgr->group()); QCOMPARE(gr->units(), cpgr->units()); QCOMPARE(gr->count(), cpgr->count()); ResourceRequest *rr = gr->resourceRequests().at(0); ResourceRequest *cprr = cpgr->resourceRequests().at(0); QCOMPARE(rr->resource(), cprr->resource()); QCOMPARE(rr->units(), cprr->units()); } +void InsertProjectXmlCommandTester::copyDependency() +{ + addResources(m_project); + + addTask(m_project, "T1"); + addTask(m_project, "T2"); + + addDependency(m_project->childNode(0), m_project->childNode(1)); + + printDebug(m_project); + + XmlSaveContext context(m_project); + context.options = XmlSaveContext::SaveNodes; + context.nodes << m_project->childNode(0) << m_project->childNode(1); + context.save(); + qInfo()<childNode(1)); + cmd.redo(); + printDebug(m_project); + QCOMPARE(m_project->allTasks().count(), 4); + + Node *copy1 = m_project->childNode(2); + Node *copy2 = m_project->childNode(3); + QVERIFY(m_project->childNode(0)->id() != copy1->id()); + QCOMPARE(m_project->childNode(0)->name(), copy1->name()); + + QCOMPARE(m_project->childNode(0)->numDependChildNodes(), copy1->numDependChildNodes()); + QCOMPARE(m_project->childNode(1)->numDependParentNodes(), copy2->numDependParentNodes()); +} QTEST_GUILESS_MAIN(KPlato::InsertProjectXmlCommandTester) diff --git a/src/libs/models/tests/InsertProjectXmlCommandTester.h b/src/libs/models/tests/InsertProjectXmlCommandTester.h index 20e218e4..4ba31450 100644 --- a/src/libs/models/tests/InsertProjectXmlCommandTester.h +++ b/src/libs/models/tests/InsertProjectXmlCommandTester.h @@ -1,53 +1,54 @@ /* This file is part of the KDE project Copyright (C) 2019 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPlato_InsertProjectXmlCommandTester_h #define KPlato_InsertProjectXmlCommandTester_h #include #include "kptresourceappointmentsmodel.h" #include "kptproject.h" #include "kptdatetime.h" namespace KPlato { class Task; class InsertProjectXmlCommandTester : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void copyBasics(); void copyRequests(); + void copyDependency(); private: void printDebug(Project *project) const; Project *m_project; }; } //namespace KPlato #endif