diff --git a/src/libs/kernel/kptproject.cpp b/src/libs/kernel/kptproject.cpp index 60c52e25..31384ce4 100644 --- a/src/libs/kernel/kptproject.cpp +++ b/src/libs/kernel/kptproject.cpp @@ -1,2947 +1,2958 @@ /* 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() ) { Relation *r = it.next(); if (context.saveNode(r->parent()) && context.saveNode(r->child())) { r->save(me, context); } } 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, 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::isStarted() const +{ + const QList tasks = allTasks(); + for (const Task *t : tasks) { + if (t->isStarted()) { + return true; + } + } + return false; +} + 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< 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. */ #ifndef KPTPROJECT_H #define KPTPROJECT_H #include "plankernel_export.h" #include "kptnode.h" #include "kptglobal.h" #include "kptaccount.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptduration.h" #include "kptresource.h" #include "kptwbsdefinition.h" #include "kptconfigbase.h" #include #include #include #include #include /// The main namespace. namespace KPlato { class Locale; class Schedule; class StandardWorktime; class ScheduleManager; class XMLLoaderObject; class Task; class SchedulerPlugin; class KPlatoXmlLoaderBase; class XmlSaveContext; /** * Project is the main node in a project, it contains child nodes and * possibly sub-projects. A sub-project is just another instantiation of this * node however. * * A note on timezones: * To be able to handle resources working in different timezones and * to facilitate data exchange with other applications like PIMs or * and groupware servers, the project has a timezone that is used for * all datetimes in nodes and schedules. * By default the local timezone is used. * * A resources timezone is defined by the associated calendar. * * Note that a projects datetimes are always displayed/modified in the timezone * it was originally created, not necessarily in your current local timezone. */ class PLANKERNEL_EXPORT Project : public Node { Q_OBJECT public: explicit Project( Node *parent = 0 ); explicit Project( ConfigBase &config, Node *parent = 0 ); explicit Project( ConfigBase &config, bool useDefaultValues, Node *parent = 0 ); ~Project(); /// Reference this project. void ref() { ++m_refCount; } /// De-reference this project. Deletes project of ref count <= 0 void deref(); /// Returns the node type. Can be Type_Project or Type_Subproject. virtual int type() const; /** * Calculate the schedules managed by the schedule manager * * @param sm Schedule manager */ void calculate( ScheduleManager &sm ); /** * Re-calculate the schedules managed by the schedule manager * * @param sm Schedule manager * @param dt The datetime from when the schedule shall be re-calculated */ void calculate( ScheduleManager &sm, const DateTime &dt ); virtual DateTime startTime( long id = -1 ) const; virtual DateTime endTime( long id = -1 ) const; /// Returns the calculated duration for schedule @p id Duration duration( long id = -1 ) const; using Node::duration; /** * 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. */ Duration *getRandomDuration(); virtual bool load( KoXmlElement &element, XMLLoaderObject &status ); virtual void save(QDomElement &element, const XmlSaveContext &context) const; using Node::saveWorkPackageXML; /// Save a workpackage document containing @p node with schedule identity @p id void saveWorkPackageXML( QDomElement &element, const Node *node, long id ) const; /** * Add the node @p task to the project, after node @p position * If @p position is zero or the project node, it will be added to this project. */ bool addTask( Node* task, Node* position ); /** * Add the node @p task to the @p parent */ bool addSubTask( Node* task, Node* parent ); /** * Add the node @p task to @p parent, in position @p index * If @p parent is zero, it will be added to this project. */ bool addSubTask( Node* task, int index, Node* parent, bool emitSignal = true ); /** * Remove the @p node. * The node is not deleted. */ void takeTask( Node *node, bool emitSignal = true ); bool canMoveTask( Node* node, Node *newParent ); bool moveTask( Node* node, Node *newParent, int newPos ); bool canIndentTask( Node* node ); bool indentTask( Node* node, int index = -1 ); bool canUnindentTask( Node* node ); bool unindentTask( Node* node ); bool canMoveTaskUp( Node* node ); bool moveTaskUp( Node* node ); bool canMoveTaskDown( Node* node ); bool moveTaskDown( Node* node ); /** * Create a task with a unique id. * The task is not added to the project. Do this with addSubTask(). */ Task *createTask(); /** * Create a copy of @p def with a unique id. * The task is not added to the project. Do this with addSubTask(). */ Task *createTask( const Task &def ); + /// @return true if any of the tasks has been started + bool isStarted() const; + int resourceGroupCount() const { return m_resourceGroups.count(); } QList &resourceGroups(); /// Adds the resource group to the project. virtual void addResourceGroup( ResourceGroup *resource, int index = -1 ); /** * Removes the resource group @p resource from the project. * The resource group is not deleted. */ ResourceGroup *takeResourceGroup( ResourceGroup *resource ); int indexOf( ResourceGroup *resource ) const { return m_resourceGroups.indexOf( resource ); } ResourceGroup *resourceGroupAt( int pos ) const { return m_resourceGroups.value( pos ); } int numResourceGroups() const { return m_resourceGroups.count(); } /// Returns the resourcegroup with identity id. ResourceGroup *group( const QString& id ); /// Returns the resource group with the matching name, 0 if no match is found. ResourceGroup *groupByName( const QString& name ) const; /** * Adds the resource to the project and resource group. * Always use this to add resources. */ void addResource( ResourceGroup *group, Resource *resource, int index = -1 ); /** * Removes the resource from the project and resource group. * The resource is not deleted. * Always use this to remove resources. */ Resource *takeResource( ResourceGroup *group, Resource *resource ); /// Move @p resource to the new @p group. Requests are removed. void moveResource( ResourceGroup *group, Resource *resource ); /// Returns the resource with identity id. Resource *resource( const QString& id ); /// Returns the resource with matching name, 0 if no match is found. Resource *resourceByName( const QString& name ) const; QStringList resourceNameList() const; /// Returns a list of all resources QList resourceList() const { return resourceIdDict.values(); } virtual EffortCostMap plannedEffortCostPrDay( QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; virtual EffortCostMap plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; using Node::plannedEffort; /// Returns the total planned effort for this project (or subproject) virtual Duration plannedEffort( long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns the total planned effort for this project (or subproject) on date virtual Duration plannedEffort( QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; using Node::plannedEffortTo; /// Returns the planned effort up to and including date virtual Duration plannedEffortTo( QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns the actual effort up to and including @p date virtual Duration actualEffortTo( QDate date ) const; /** * Planned cost up to and including date * @param date The cost is calculated from the start of the project upto including date. * @param id Identity of the schedule to be used. * @param typ the type of calculation. * @sa EffortCostCalculationType */ virtual double plannedCostTo( QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /** * Actual cost up to and including @p date * @param id Identity of the schedule to be used. * @param date The cost is calculated from the start of the project upto including date. */ virtual EffortCost actualCostTo( long int id, QDate date ) const; virtual EffortCostMap actualEffortCostPrDay( QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; virtual EffortCostMap actualEffortCostPrDay( const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; double effortPerformanceIndex( QDate date, long id ) const; double schedulePerformanceIndex( QDate date, long id ) const; /// Returns the effort planned to be used to reach the actual percent finished virtual Duration budgetedWorkPerformed( QDate date, long id = CURRENTSCHEDULE ) const; /// Returns the cost planned to be used to reach the actual percent finished virtual double budgetedCostPerformed( QDate date, long id = CURRENTSCHEDULE ) const; /// Budgeted Cost of Work Scheduled ( up to @p date ) virtual double bcws( QDate date, long id = BASELINESCHEDULE ) const; /// Budgeted Cost of Work Performed virtual double bcwp( long id = BASELINESCHEDULE ) const; /// Budgeted Cost of Work Performed ( up to @p date ) virtual double bcwp( QDate date, long id = BASELINESCHEDULE ) const; Calendar *defaultCalendar() const { return m_defaultCalendar; } void setDefaultCalendar( Calendar *cal ); const QList &calendars() const; void addCalendar( Calendar *calendar, Calendar *parent = 0, int index = -1 ); void takeCalendar( Calendar *calendar ); int indexOf( const Calendar *calendar ) const; /// Returns the calendar with identity id. Calendar *calendar( const QString& id ) const; /// Returns a list of all calendars QStringList calendarNames() const; /// Find calendar by name Calendar *calendarByName( const QString &name ) const; void changed( Calendar *cal ); QList allCalendars() const; /// Return number of calendars int calendarCount() const { return m_calendars.count(); } /// Return the calendar at @p index, 0 if index out of bounds Calendar *calendarAt( int index ) const { return m_calendars.value( index ); } /** * Defines the length of days, weeks, months and years * and the standard working week. * Used for estimation and calculation of effort, * and presentation in gantt chart. */ StandardWorktime *standardWorktime() { return m_standardWorktime; } void setStandardWorktime( StandardWorktime * worktime ); void changed( StandardWorktime* ); /// Check if a link exists between node @p par and @p child. bool linkExists( const Node *par, const Node *child ) const; /// Check if node @p par can be linked to node @p child. bool legalToLink( const Node *par, const Node *child ) const; using Node::legalToLink; virtual const QHash &nodeDict() { return nodeIdDict; } /// Return a list of all nodes in the project (excluding myself) QList allNodes() const; /// Return the number of all nodes in the project (excluding myself) int nodeCount() const { return nodeIdDict.count() - 1; } /// Return a list of all tasks and milestones int the wbs order QList allTasks( const Node *parent = 0 ) const; using Node::findNode; /// Find the node with identity id virtual Node *findNode( const QString &id ) const; using Node::removeId; /// Remove the node with identity id from the registers virtual bool removeId( const QString &id ); /// Reserve @p id for the @p node virtual void reserveId( const QString &id, Node *node ); /// Register @p node. The nodes id must be unique and non-empty. bool registerNodeId( Node *node ); /// Create a unique id. QString uniqueNodeId( int seed = 1 ) const; /// Check if node @p id is used bool nodeIdentExists( const QString &id ) const; /// Create a unique id. QString uniqueNodeId( const QList &existingIds, int seed = 1 ); ResourceGroup *findResourceGroup( const QString &id ) const { if ( resourceGroupIdDict.contains( id ) ) return resourceGroupIdDict[ id ]; return 0; } /// Remove the resourcegroup with identity id from the register bool removeResourceGroupId( const QString &id ) { if ( resourceGroupIdDict.contains( id ) ) return resourceGroupIdDict.remove( id ); return false; } /// Insert the resourcegroup with identity id void insertResourceGroupId( const QString &id, ResourceGroup* group ) { resourceGroupIdDict.insert( id, group ); } /// Generate, set and insert unique id bool setResourceGroupId( ResourceGroup *group); /// returns a unique resourcegroup id QString uniqueResourceGroupId() const; /// Return a list of resources that will be allocated to new tasks QList autoAllocateResources() const; Resource *findResource( const QString &id ) const { if ( resourceIdDict.contains( id ) ) return resourceIdDict[ id ]; return 0; } /// Remove the resource with identity id from the register bool removeResourceId( const QString &id ); /// Insert the resource with identity id void insertResourceId( const QString &id, Resource *resource ); /// Generate, set and insert unique id bool setResourceId( Resource *resource ); /// returns a unique resource id QString uniqueResourceId() const; /// Find the calendar with identity id virtual Calendar *findCalendar( const QString &id ) const { if ( id.isEmpty() || !calendarIdDict.contains( id ) ) return 0; return calendarIdDict[ id ]; } /// Remove the calendar with identity id from the register virtual bool removeCalendarId( const QString &id ); /// Insert the calendar with identity id virtual void insertCalendarId( const QString &id, Calendar *calendar ); /// Set and insert a unique id for calendar bool setCalendarId( Calendar *calendar ); /// returns a unique calendar id QString uniqueCalendarId() const; /// Return reference to WBS Definition WBSDefinition &wbsDefinition(); /// Set WBS Definition to @p def void setWbsDefinition( const WBSDefinition &def ); /// Generate WBS Code virtual QString generateWBSCode( QList &indexes, bool sortable = false ) const; Accounts &accounts() { return m_accounts; } const Accounts &accounts() const { return m_accounts; } /** * Set current schedule to the schedule with identity @p id, for me and my children * Note that this is used (and may be changed) when calculating schedules */ virtual void setCurrentSchedule( long id ); /// Create new schedule with unique name and id of type Expected. MainSchedule *createSchedule(); /// Create new schedule with unique id. MainSchedule *createSchedule( const QString& name, Schedule::Type type ); /// Add the schedule to the project. A fresh id will be generated for the schedule. void addMainSchedule( MainSchedule *schedule ); /// Set parent schedule for my children virtual void setParentSchedule( Schedule *sch ); /// Find the schedule manager that manages the Schedule with @p id ScheduleManager *scheduleManager( long id ) const; /// Find the schedule manager with @p id ScheduleManager *scheduleManager( const QString &id ) const; /// Create a unique schedule name (This may later be changed by the user) QString uniqueScheduleName() const; /// Create a unique schedule manager identity QString uniqueScheduleManagerId() const; ScheduleManager *createScheduleManager(); ScheduleManager *createScheduleManager( const QString &name ); /// Returns a list of all top level schedule managers QList scheduleManagers() const { return m_managers; } int numScheduleManagers() const { return m_managers.count(); } int indexOf( const ScheduleManager *sm ) const { return m_managers.indexOf( const_cast(sm) ); } bool isScheduleManager( void* ptr ) const; void addScheduleManager( ScheduleManager *sm, ScheduleManager *parent = 0, int index = -1 ); int takeScheduleManager( ScheduleManager *sm ); void moveScheduleManager( ScheduleManager *sm, ScheduleManager *newparent = 0, int newindex = -1 ); ScheduleManager *findScheduleManagerByName( const QString &name ) const; /// Returns a list of all schedule managers QList allScheduleManagers() const; /// Return true if schedule with identity @p id is baselined bool isBaselined( long id = ANYSCHEDULED ) const; void changed( ResourceGroup *group ); void changed( Resource *resource ); void changed( ScheduleManager *sm ); void changed( MainSchedule *sch ); void sendScheduleAdded( const MainSchedule *sch ); void sendScheduleToBeAdded( const ScheduleManager *manager, int row ); void sendScheduleRemoved( const MainSchedule *sch ); void sendScheduleToBeRemoved( const MainSchedule *sch ); /// Return the time zone used in this project QTimeZone timeZone() const { return m_timeZone; } /// Set the time zone to be used in this project void setTimeZone( const QTimeZone &tz ) { m_timeZone = tz; } /** * Add a relation between the nodes specified in the relation rel. * Emits signals relationToBeAdded() before the relation is added, * and relationAdded() after it has been added. * @param rel The relation to be added. * @param check If true, the relation is checked for validity * @return true if successful. */ bool addRelation( Relation *rel, bool check=true ); /** * Removes the relation @p rel without deleting it. * Emits signals relationToBeRemoved() before the relation is removed, * and relationRemoved() after it has been removed. */ void takeRelation( Relation *rel ); /** * Modify the @p type of the @p relation. */ void setRelationType( Relation *relation, Relation::Type type ); /** * Modify the @p lag of the @p relation. */ void setRelationLag( Relation *relation, const Duration &lag ); void calcCriticalPathList( MainSchedule *cs ); void calcCriticalPathList( MainSchedule *cs, Node *node ); /** * Returns the list of critical paths for schedule @p id */ const QList< QList > *criticalPathList( long id = CURRENTSCHEDULE ); QList criticalPath( long id = CURRENTSCHEDULE, int index = 0 ); /// Returns a flat list af all nodes QList flatNodeList( Node *parent = 0 ); void generateUniqueNodeIds(); void generateUniqueIds(); const ConfigBase &config() const { return m_config ? *m_config : emptyConfig; } /// Set configuration data void setConfig( ConfigBase *config ) { m_config = config; } const Task &taskDefaults() const { return config().taskDefaults(); } /// Return locale. (Used for currency, everything else is from KGlobal::locale) Locale *locale() { return const_cast(config()).locale(); } /// Return locale. (Used for currency, everything else is from KGlobal::locale) const Locale *locale() const { return config().locale(); } /// Signal that locale data has changed void emitLocaleChanged(); void setSchedulerPlugins( const QMap &plugins ); const QMap &schedulerPlugins() const { return m_schedulerPlugins; } void initiateCalculation( MainSchedule &sch ); void initiateCalculationLists( MainSchedule &sch ); void finishCalculation( ScheduleManager &sm ); void adjustSummarytask(); /// Increments progress and emits signal sigProgress() void incProgress(); /// Emits signal maxProgress() void emitMaxProgress( int value ); bool stopcalculation; /// return a map of all external projects QMap externalProjects() const; void emitDocumentAdded( Node*, Document*, int index ); void emitDocumentRemoved( Node*, Document*, int index ); void emitDocumentChanged( Node*, Document*, int index ); bool useSharedResources() const; void setUseSharedResources(bool on); bool isSharedResourcesLoaded() const; void setSharedResourcesLoaded(bool on); void setSharedResourcesFile(const QString &file); QString sharedResourcesFile() const; void setSharedProjectsUrl(const QUrl &url); QUrl sharedProjectsUrl() const; void setLoadProjectsAtStartup(bool value); bool loadProjectsAtStartup() const; public Q_SLOTS: /// Sets m_progress to @p progress and emits signal sigProgress() /// If @p sm is not 0, progress is also set for the schedule manager void setProgress(int progress, KPlato::ScheduleManager *sm = 0); /// Sets m_maxprogress to @p max and emits signal maxProgress() /// If @p sm is not 0, max progress is also set for the schedule manager void setMaxProgress(int max, KPlato::ScheduleManager *sm = 0 ); Q_SIGNALS: /// Emitted when the project is about to be deleted (The destroyed signal is disabled) void aboutToBeDeleted(); /// Emitted when anything in the project is changed (use with care) void projectChanged(); /// Emitted when the WBS code definition has changed. This may change all nodes. void wbsDefinitionChanged(); /// Emitted when a schedule has been calculated void projectCalculated(KPlato::ScheduleManager *sm); /// Emitted when the pointer to the current schedule has been changed void currentScheduleChanged(); /// Use to show progress during calculation void sigProgress( int ); /// Use to set the maximum progress (minimum always 0) void maxProgress( int ); /// Emitted when calculation starts void sigCalculationStarted(KPlato::Project *project, KPlato::ScheduleManager *sm); /// Emitted when calculation is finished void sigCalculationFinished(KPlato::Project *project, KPlato::ScheduleManager *sm); /// This signal is emitted when one of the nodes members is changed. void nodeChanged(KPlato::Node*); /// This signal is emitted when the node is to be added to the project. void nodeToBeAdded(KPlato::Node*, int); /// This signal is emitted when the node has been added to the project. void nodeAdded(KPlato::Node*); /// This signal is emitted when the node is to be removed from the project. void nodeToBeRemoved(KPlato::Node*); /// This signal is emitted when the node has been removed from the project. void nodeRemoved(KPlato::Node*); /// This signal is emitted when the node is to be moved up, moved down, indented or unindented. void nodeToBeMoved(KPlato::Node* node, int pos, KPlato::Node* newParent, int newPos); /// This signal is emitted when the node has been moved up, moved down, indented or unindented. void nodeMoved(KPlato::Node*); /// This signal is emitted when a document is added void documentAdded(KPlato::Node*, KPlato::Document*, int index); /// This signal is emitted when a document is removed void documentRemoved(KPlato::Node*, KPlato::Document*, int index); /// This signal is emitted when a document is changed void documentChanged(KPlato::Node*, KPlato::Document*, int index); void resourceGroupChanged(KPlato::ResourceGroup *group); void resourceGroupAdded(const KPlato::ResourceGroup *group); void resourceGroupToBeAdded(const KPlato::ResourceGroup *group, int row); void resourceGroupRemoved(const KPlato::ResourceGroup *group); void resourceGroupToBeRemoved(const KPlato::ResourceGroup *group); void resourceChanged(KPlato::Resource *resource); void resourceAdded(const KPlato::Resource *resource); void resourceToBeAdded(const KPlato::ResourceGroup *group, int row); void resourceRemoved(const KPlato::Resource *resource); void resourceToBeRemoved(const KPlato::Resource *resource); void scheduleManagerChanged(KPlato::ScheduleManager *sch); void scheduleManagerAdded(const KPlato::ScheduleManager *sch); void scheduleManagerToBeAdded(const KPlato::ScheduleManager *sch, int row); void scheduleManagerRemoved(const KPlato::ScheduleManager *sch); void scheduleManagerToBeRemoved(const KPlato::ScheduleManager *sch); void scheduleManagerMoved(const KPlato::ScheduleManager *sch, int row); void scheduleManagerToBeMoved(const KPlato::ScheduleManager *sch); void scheduleChanged(KPlato::MainSchedule *sch); void scheduleToBeAdded(const KPlato::ScheduleManager *manager, int row); void scheduleAdded(const KPlato::MainSchedule *sch); void scheduleToBeRemoved(const KPlato::MainSchedule *sch); void scheduleRemoved(const KPlato::MainSchedule *sch); // void currentViewScheduleIdChanged( long id ); void calendarChanged(KPlato::Calendar *cal); void calendarToBeAdded(const KPlato::Calendar *cal, int row); void calendarAdded(const KPlato::Calendar *cal); void calendarToBeRemoved(const KPlato::Calendar *cal); void calendarRemoved(const KPlato::Calendar *cal); /** * Emitted when the default calendar pointer has changed * @param cal The new default calendar. May be 0. */ void defaultCalendarChanged(KPlato::Calendar *cal); /** * Emitted when the standard worktime has been changed. */ void standardWorktimeChanged(KPlato::StandardWorktime*); /// Emitted when the relation @p rel is about to be added. void relationToBeAdded(KPlato::Relation *rel, int parentIndex, int childIndex); /// Emitted when the relation @p rel has been added. void relationAdded(KPlato::Relation *rel); /// Emitted when the relation @p rel is about to be removed. void relationToBeRemoved(KPlato::Relation *rel); /// Emitted when the relation @p rel has been removed. void relationRemoved(KPlato::Relation *rel); /// Emitted when the relation @p rel shall be modified. void relationToBeModified(KPlato::Relation *rel); /// Emitted when the relation @p rel has been modified. void relationModified(KPlato::Relation *rel); /// Emitted when locale data has changed void localeChanged(); protected: /// Calculate the schedule. void calculate( Schedule *scedule ); /// Calculate current schedule void calculate(); /// Re-calculate the schedule from @p dt void calculate( Schedule *scedule, const DateTime &dt ); /// Calculate current schedule from @p dt (Always calculates forward) void calculate( const DateTime &dt ); /// Calculate critical path virtual bool calcCriticalPath( bool fromEnd ); /// Prepare task lists for scheduling void tasksForward(); /// Prepare task lists for scheduling void tasksBackward(); protected: friend class KPlatoXmlLoaderBase; using Node::changed; virtual void changed(Node *node, int property = -1); Accounts m_accounts; QList m_resourceGroups; QList m_calendars; Calendar * m_defaultCalendar; StandardWorktime *m_standardWorktime; DateTime calculateForward( int use ); DateTime calculateBackward( int use ); DateTime scheduleForward( const DateTime &earliest, int use ); DateTime scheduleBackward( const DateTime &latest, int use ); DateTime checkStartConstraints( const DateTime &dt ) const; DateTime checkEndConstraints( const DateTime &dt ) const; bool legalParents( const Node *par, const Node *child ) const; bool legalChildren( const Node *par, const Node *child ) const; #ifndef PLAN_NLOGDEBUG private: static bool checkParent( Node *n, const QList &list, QList &checked ); static bool checkChildren( Node *n, const QList &list, QList &checked ); #endif private: void init(); QHash resourceGroupIdDict; QHash resourceIdDict; QHash nodeIdDict; QMap nodeIdReserved; QMap calendarIdDict; QMap m_managerIdMap; QList m_managers; QTimeZone m_timeZone; WBSDefinition m_wbsDefinition; ConfigBase emptyConfig; QPointer m_config; // this one is not owned by me, don't delete int m_progress; QMap m_schedulerPlugins; int m_refCount; // make it possible to use the project by different threads QList m_hardConstraints; QList m_softConstraints; QList m_terminalNodes; bool m_useSharedResources; bool m_sharedResourcesLoaded; QString m_sharedResourcesFile; QUrl m_sharedProjectsUrl; bool m_loadProjectsAtStartup; }; } //KPlato namespace #endif diff --git a/src/libs/kernel/kpttask.cpp b/src/libs/kernel/kpttask.cpp index 56e20c1d..7472f674 100644 --- a/src/libs/kernel/kpttask.cpp +++ b/src/libs/kernel/kpttask.cpp @@ -1,3851 +1,3856 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2004 - 2007 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 "kpttask.h" #include "kptappointment.h" #include "kptproject.h" #include "kptduration.h" #include "kptrelation.h" #include "kptdatetime.h" #include "kptcalendar.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include #include #include namespace KPlato { Task::Task(Node *parent) : Node(parent), m_resource(), m_workPackage( this ) { //debugPlan<<"("<setOptimisticRatio(-10); m_estimate->setPessimisticRatio(20); m_estimate->setParentNode( this ); if (m_parent) m_leader = m_parent->leader(); } Task::Task(const Task &task, Node *parent) : Node(task, parent), m_resource(), m_workPackage( this ) { //debugPlan<<"("<setParentNode( this ); } Task::~Task() { while (!m_resource.isEmpty()) { delete m_resource.takeFirst(); } while (!m_parentProxyRelations.isEmpty()) { delete m_parentProxyRelations.takeFirst(); } while (!m_childProxyRelations.isEmpty()) { delete m_childProxyRelations.takeFirst(); } } int Task::type() const { if ( numChildren() > 0) { return Node::Type_Summarytask; } else if ( m_constraint == Node::FixedInterval ) { if ( m_constraintEndTime == m_constraintStartTime ) { return Node::Type_Milestone; } } else if ( m_estimate->expectedEstimate() == 0.0 ) { return Node::Type_Milestone; } return Node::Type_Task; } Duration *Task::getRandomDuration() { return 0L; } ResourceGroupRequest *Task::resourceGroupRequest(const ResourceGroup *group) const { return m_requests.find(group); } void Task::clearResourceRequests() { m_requests.clear(); changed( this ); } void Task::addRequest(ResourceGroup *group, int numResources) { addRequest(new ResourceGroupRequest(group, numResources)); } void Task::addRequest(ResourceGroupRequest *request) { //debugPlan<group()<group()->id()<group()->name(); m_requests.addRequest(request); changed( this ); } void Task::takeRequest(ResourceGroupRequest *request) { //debugPlan< Task::requestedResources() const { return m_requests.requestedResources(); } bool Task::containsRequest( const QString &identity ) const { return m_requests.contains( identity ); } ResourceRequest *Task::resourceRequest( const QString &name ) const { return m_requests.resourceRequest( name ); } QStringList Task::assignedNameList( long id) const { Schedule *s = schedule( id ); if ( s == 0 ) { return QStringList(); } return s->resourceNameList(); } void Task::makeAppointments() { if (m_currentSchedule == 0) return; if (type() == Node::Type_Task) { //debugPlan<startTime<<","<endTime<<";"<duration.toString(); m_requests.makeAppointments(m_currentSchedule); //debugPlan<startTime<<","<endTime<<";"<duration.toString(); } else if (type() == Node::Type_Summarytask) { foreach (Node *n, m_nodes) { n->makeAppointments(); } } else if (type() == Node::Type_Milestone) { //debugPlan<<"Milestone not implemented"; // Well, shouldn't have resources anyway... } } void Task::copySchedule() { if ( m_currentSchedule == 0 || type() != Node::Type_Task ) { return; } int id = m_currentSchedule->parentScheduleId(); NodeSchedule *ns = static_cast( findSchedule( id ) ); if ( ns == 0 ) { return; } if ( type() == Node::Type_Task ) { copyAppointments( ns->startTime, ns->endTime ); } m_currentSchedule->startTime = ns->startTime; m_currentSchedule->earlyStart = ns->earlyStart; m_currentSchedule->endTime = ns->endTime; m_currentSchedule->lateFinish = ns->lateFinish; m_currentSchedule->duration = ns->duration; // TODO: status flags, etc //debugPlan; } void Task::copyAppointments() { copyAppointments( DateTime(), m_currentSchedule->startTime ); } void Task::copyAppointments( const DateTime &start, const DateTime &end ) { if ( m_currentSchedule == 0 || type() != Node::Type_Task ) { return; } int id = m_currentSchedule->parentScheduleId(); NodeSchedule *ns = static_cast( findSchedule( id ) ); if ( ns == 0 ) { return; } DateTime st = start.isValid() ? start : ns->startTime; DateTime et = end.isValid() ? end : ns->endTime; //debugPlan<calculationMode(); foreach ( const Appointment *a, ns->appointments() ) { Resource *r = a->resource() == 0 ? 0 : a->resource()->resource(); if ( r == 0 ) { errorPlan<<"No resource"; continue; } AppointmentIntervalList lst = a->intervals( st, et ); if ( lst.isEmpty() ) { //debugPlan<<"No intervals to copy from"<appointments() ) { if ( c->resource()->resource() == r ) { //debugPlan<<"Found current appointment to"<resource()->resource()->name()<add( curr ); curr->setNode( m_currentSchedule ); //debugPlan<<"Created new appointment"<( r->findSchedule( m_currentSchedule->id() ) ); if ( rs == 0 ) { rs = r->createSchedule( m_currentSchedule->parent() ); rs->setId( m_currentSchedule->id() ); rs->setName( m_currentSchedule->name() ); rs->setType( m_currentSchedule->type() ); //debugPlan<<"Resource schedule not found, id="<id(); } rs->setCalculationMode( m_currentSchedule->calculationMode() ); if ( ! rs->appointments().contains( curr ) ) { //debugPlan<<"add to resource"<add( curr ); curr->setResource( rs ); } Appointment app; app.setIntervals( lst ); //foreach ( AppointmentInterval *i, curr->intervals() ) { debugPlan<startTime().toString()<endTime().toString(); } curr->merge( app ); //debugPlan<<"Appointments added"; } m_currentSchedule->startTime = ns->startTime; m_currentSchedule->earlyStart = ns->earlyStart; } void Task::calcResourceOverbooked() { if (m_currentSchedule) m_currentSchedule->calcResourceOverbooked(); } bool Task::load(KoXmlElement &element, XMLLoaderObject &status ) { QString s; bool ok = false; m_id = element.attribute(QStringLiteral("id")); setName( element.attribute(QStringLiteral("name")) ); m_leader = element.attribute(QStringLiteral("leader")); m_description = element.attribute(QStringLiteral("description")); //debugPlan<load(e)) { if (!project.addSubTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; }*/ } else if (e.tagName() == QLatin1String("task")) { if (status.loadTaskChildren()) { // Load the task Task *child = new Task(this); if (child->load(e, status)) { if (!status.project().addSubTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; } } } else if (e.tagName() == QLatin1String("resource")) { // TODO: Load the resource (projects don't have resources yet) } else if (e.tagName() == QLatin1String("estimate") || ( /*status.version() < "0.6" &&*/ e.tagName() == QLatin1String("effort") ) ) { // Load the estimate m_estimate->load(e, status); } else if (e.tagName() == QLatin1String("resourcegroup-request")) { // Load the resource request // Handle multiple requests to same group gracefully (Not really allowed) ResourceGroupRequest *r = m_requests.findGroupRequestById( e.attribute(QStringLiteral("group-id")) ); if ( r ) { warnPlan<<"Multiple requests to same group, loading into existing group"; if ( ! r->load( e, status ) ) { errorPlan<<"Failed to load resource request"; } } else { r = new ResourceGroupRequest(); if (r->load(e, status)) { addRequest(r); } else { errorPlan<<"Failed to load resource request"; delete r; } } } else if (e.tagName() == QLatin1String("workpackage")) { m_workPackage.loadXML( e, status ); } else if (e.tagName() == QLatin1String("progress")) { completion().loadXML( e, status ); } else if (e.tagName() == QLatin1String("schedules")) { KoXmlNode n = e.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == QLatin1String("schedule")) { NodeSchedule *sch = new NodeSchedule(); if (sch->loadXML(el, status)) { sch->setNode(this); addSchedule(sch); } else { errorPlan<<"Failed to load schedule"; delete sch; } } } } else if (e.tagName() == QLatin1String("documents")) { m_documents.load( e, status ); } else if (e.tagName() == QLatin1String("workpackage-log")) { KoXmlNode n = e.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == QLatin1String("workpackage")) { WorkPackage *wp = new WorkPackage( this ); if ( wp->loadLoggedXML( el, status ) ) { m_packageLog << wp; } else { errorPlan<<"Failed to load logged workpackage"; delete wp; } } } } } //debugPlan<save(me); m_documents.save( me ); if ( ! m_requests.isEmpty() ) { m_requests.save(me); } if (context.saveAll(this)) { if (!m_schedules.isEmpty()) { QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules")); me.appendChild(schs); foreach (const Schedule *s, m_schedules) { if (!s->isDeleted()) { s->saveXML(schs); } } } completion().saveXML( me ); m_workPackage.saveXML(me); // The workpackage log if (!m_packageLog.isEmpty()) { QDomElement log = me.ownerDocument().createElement(QStringLiteral("workpackage-log")); me.appendChild(log); foreach (const WorkPackage *wp, m_packageLog) { wp->saveLoggedXML( log ); } } } if (context.saveChildren(this)) { for (int i=0; isave(me, context); } } } void Task::saveAppointments(QDomElement &element, long id) const { //debugPlan<save(me); completion().saveXML( me ); if ( m_schedules.contains( id ) && ! m_schedules[ id ]->isDeleted() ) { QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules")); me.appendChild(schs); m_schedules[ id ]->saveXML( schs ); } m_documents.save( me ); // TODO: copying documents } +bool Task::isStarted() const +{ + return completion().isStarted(); +} + EffortCostMap Task::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; } Schedule *s = schedule( id ); if ( s ) { return s->plannedEffortCostPrDay( start, end, typ ); } return EffortCostMap(); } EffortCostMap Task::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; } Schedule *s = schedule( id ); if ( s ) { return s->plannedEffortCostPrDay( resource, start, end, typ ); } return EffortCostMap(); } EffortCostMap Task::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; } switch ( completion().entrymode() ) { case Completion::FollowPlan: return plannedEffortCostPrDay( start, end, id, typ ); default: return completion().effortCostPrDay( start, end, id ); } return EffortCostMap(); } EffortCostMap Task::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; } switch ( completion().entrymode() ) { case Completion::FollowPlan: return plannedEffortCostPrDay( resource, start, end, id, typ ); default: return completion().effortCostPrDay( resource, start, end ); } return EffortCostMap(); } // Returns the total planned effort for this task (or subtasks) Duration Task::plannedEffort( const Resource *resource, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort( resource, id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffort( resource, typ ); } return eff; } // Returns the total planned effort for this task (or subtasks) Duration Task::plannedEffort( long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort( id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffort( typ ); } return eff; } // Returns the total planned effort for this task (or subtasks) on date Duration Task::plannedEffort( const Resource *resource, QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort( resource, date, id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffort( resource, date, typ ); } return eff; } // Returns the total planned effort for this task (or subtasks) on date Duration Task::plannedEffort(QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort( date, id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffort( date, typ ); } return eff; } // Returns the total planned effort for this task (or subtasks) upto and including date Duration Task::plannedEffortTo( QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffortTo( date, id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffortTo( date, typ ); } return eff; } // Returns the total planned effort for this task (or subtasks) upto and including date Duration Task::plannedEffortTo( const Resource *resource, QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffortTo( resource, date, id, typ ); } return eff; } Schedule *s = schedule( id ); if ( s ) { eff = s->plannedEffortTo( resource, date, typ ); } return eff; } // Returns the total actual effort for this task (or subtasks) Duration Task::actualEffort() const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffort(); } } return completion().actualEffort(); } // Returns the total actual effort for this task (or subtasks) on date Duration Task::actualEffort( QDate date ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffort( date ); } return eff; } return completion().actualEffort( date ); } // Returns the total actual effort for this task (or subtasks) to date Duration Task::actualEffortTo( QDate date ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffortTo( date ); } return eff; } return completion().actualEffortTo( date ); } EffortCost Task::plannedCost( long id, EffortCostCalculationType typ ) const { //debugPlan; if (type() == Node::Type_Summarytask) { return Node::plannedCost( id, typ ); } EffortCost c; Schedule *s = schedule( id ); if ( s ) { c = s->plannedCost( typ ); } return c; } double Task::plannedCostTo( QDate date, long id, EffortCostCalculationType typ ) const { //debugPlan; double c = 0; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { c += n->plannedCostTo( date, id, typ ); } return c; } Schedule *s = schedule( id ); if ( s == 0 ) { return c; } c = s->plannedCostTo( date, typ ); if ( date >= s->startTime.date() ) { c += m_startupCost; } if ( date >= s->endTime.date() ) { c += m_shutdownCost; } return c; } EffortCost Task::actualCostTo( long int id, QDate date ) const { //debugPlan; EffortCostMap ecm = acwp( id ); return EffortCost( ecm.effortTo( date ), ecm.costTo( date ) ); } double Task::bcws( QDate date, long id ) const { //debugPlan; double c = plannedCostTo( date, id ); //debugPlan<bcwsPrDayCache( typ ); if ( ! cache.cached ) { EffortCostMap ec = s->bcwsPrDay( typ ); if ( typ != ECCT_Work ) { if ( m_startupCost > 0.0 ) { ec.add( s->startTime.date(), Duration::zeroDuration, m_startupCost ); } if ( m_shutdownCost > 0.0 ) { ec.add( s->endTime.date(), Duration::zeroDuration, m_shutdownCost ); } cache.effortcostmap = ec; cache.cached = true; } } return cache.effortcostmap; } EffortCostMap Task::bcwpPrDay( long int id, EffortCostCalculationType typ ) { //debugPlan; if ( type() == Node::Type_Summarytask ) { return Node::bcwpPrDay( id, typ ); } Schedule *s = schedule( id ); if ( s == 0 ) { return EffortCostMap(); } EffortCostCache cache = s->bcwpPrDayCache( typ ); if ( ! cache.cached ) { // do not use bcws cache, it includes startup/shutdown cost EffortCostMap e = s->plannedEffortCostPrDay( s->appointmentStartTime().date(), s->appointmentEndTime().date(), typ ); if ( completion().isStarted() && ! e.isEmpty() ) { // calculate bcwp on bases of bcws *without* startup/shutdown cost double totEff = e.totalEffort().toDouble( Duration::Unit_h ); double totCost = e.totalCost(); QDate sd = completion().entries().keys().value( 0 ); if ( ! sd.isValid() || e.startDate() < sd ) { sd = e.startDate(); } QDate ed = qMax( e.endDate(), completion().entryDate() ); for ( QDate d = sd; d <= ed; d = d.addDays( 1 ) ) { double p = (double)(completion().percentFinished( d )) / 100.0; EffortCost ec = e.days()[ d ]; ec.setBcwpEffort( totEff * p ); ec.setBcwpCost( totCost * p ); e.insert( d, ec ); } } if ( typ != ECCT_Work ) { // add bcws startup/shutdown cost if ( m_startupCost > 0.0 ) { e.add( s->startTime.date(), Duration::zeroDuration, m_startupCost ); } if ( m_shutdownCost > 0.0 ) { e.add( s->endTime.date(), Duration::zeroDuration, m_shutdownCost ); } // add bcwp startup/shutdown cost if ( m_shutdownCost > 0.0 && completion().finishIsValid() ) { QDate finish = completion().finishTime().date(); e.addBcwpCost( finish, m_shutdownCost ); debugPlan<<"addBcwpCost:"< finish ) { e.addBcwpCost( date, m_shutdownCost ); debugPlan<<"addBcwpCost:"< 0.0 && completion().startIsValid() ) { QDate start = completion().startTime().date(); e.addBcwpCost( start, m_startupCost ); // bcwp is cumulative so add to all entries after start for ( EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it ) { const QDate date = it.key(); if ( date > start ) { e.addBcwpCost( date, m_startupCost ); } } } } cache.effortcostmap = e; cache.cached = true; } return cache.effortcostmap; } Duration Task::budgetedWorkPerformed( QDate date, long id ) const { //debugPlan; Duration e; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { e += n->budgetedWorkPerformed( date, id ); } return e; } e = plannedEffort( id ) * (double)completion().percentFinished( date ) / 100.0; //debugPlan<budgetedCostPerformed( date, id ); } return c; } c = plannedCost( id ).cost() * (double)completion().percentFinished( date ) / 100.0; if ( completion().isStarted() && date >= completion().startTime().date() ) { c += m_startupCost; } if ( completion().isFinished() && date >= completion().finishTime().date() ) { c += m_shutdownCost; } //debugPlan<acwpCache( typ ); if ( ! ec.cached ) { //debugPlan<= completion().startTime().date() ) { c.add( Duration::zeroDuration, m_startupCost ); } if ( completion().isFinished() && date >= completion().finishTime().date() ) { c.add( Duration::zeroDuration, m_shutdownCost ); } return c; } double Task::schedulePerformanceIndex( QDate date, long id ) const { //debugPlan; double r = 1.0; double s = bcws( date, id ); double p = bcwp( date, id ); if ( s > 0.0 ) { r = p / s; } return r; } double Task::effortPerformanceIndex( QDate date, long id ) const { //debugPlan; double r = 1.0; Duration a, b; if ( m_estimate->type() == Estimate::Type_Effort ) { Duration b = budgetedWorkPerformed( date, id ); if ( b == Duration::zeroDuration ) { return r; } Duration a = actualEffortTo( date ); if ( b == Duration::zeroDuration ) { return 1.0; } r = b.toDouble() / a.toDouble(); } else if ( m_estimate->type() == Estimate::Type_Duration ) { //TODO } return r; } //FIXME Handle summarytasks double Task::costPerformanceIndex( long int id, QDate date, bool *error ) const { double res = 0.0; double ac = actualCostTo( id, date ).cost(); bool e = ( ac == 0.0 || completion().percentFinished() == 0 ); if (error) { *error = e; } if (!e) { res = ( plannedCostTo( date, id ) * completion().percentFinished() ) / ( 100 * ac ); } return res; } void Task::initiateCalculation(MainSchedule &sch) { //debugPlan< &list, int use) { DateTime time; // do them forward foreach (Relation *r, list) { if (r->parent()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } DateTime t = r->parent()->calculateForward(use); // early finish switch (r->type()) { case Relation::StartStart: // I can't start earlier than my predesseccor t = r->parent()->earlyStart() + r->lag(); break; case Relation::FinishFinish: { // I can't finish earlier than my predeccessor, so // I can't start earlier than it's (earlyfinish+lag)- my duration t += r->lag(); Schedule::OBState obs = m_currentSchedule->allowOverbookingState(); m_currentSchedule->setAllowOverbookingState( Schedule::OBS_Allow ); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( QStringLiteral("FinishFinish: get duration to calculate early finish") ); #endif t -= duration(t, use, true); m_currentSchedule->setAllowOverbookingState( obs ); break; } default: t += r->lag(); break; } if (!time.isValid() || t > time) time = t; } //debugPlan<earlyFinish; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode( Schedule::CalculateForward ); //cs->logDebug( "calculateForward: earlyStart=" + cs->earlyStart.toString() ); // calculate all predecessors if (!dependParentNodes().isEmpty()) { DateTime time = calculatePredeccessors(dependParentNodes(), use); if (time.isValid() && time > cs->earlyStart) { cs->earlyStart = time; //cs->logDebug( QString( "calculate forward: early start moved to: %1" ).arg( cs->earlyStart.toString() ) ); } } if (!m_parentProxyRelations.isEmpty()) { DateTime time = calculatePredeccessors(m_parentProxyRelations, use); if (time.isValid() && time > cs->earlyStart) { cs->earlyStart = time; //cs->logDebug( QString( "calculate forward: early start moved to: %1" ).arg( cs->earlyStart.toString() ) ); } } m_calculateForwardRun = true; //cs->logDebug( "calculateForward: earlyStart=" + cs->earlyStart.toString() ); return calculateEarlyFinish( use ); } DateTime Task::calculateEarlyFinish(int use) { //debugPlan<usePert(); cs->setCalculationMode( Schedule::CalculateForward ); #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug( QStringLiteral( "Start calculate forward: %1 " ).arg( constraintToString( true ) ) ); #endif QLocale locale; cs->logInfo( i18n( "Calculate early finish " ) ); //debugPlan<<"------>"<earlyStart; if (type() == Node::Type_Task) { m_durationForward = m_estimate->value(use, pert); switch (constraint()) { case Node::ASAP: case Node::ALAP: { //debugPlan<earlyStart; cs->earlyStart = workTimeAfter( cs->earlyStart ); m_durationForward = duration(cs->earlyStart, use, false); m_earlyFinish = cs->earlyStart + m_durationForward; #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + m_earlyFinish.toString() ); #endif if ( !cs->allowOverbooking() ) { cs->startTime = cs->earlyStart; cs->endTime = m_earlyFinish; makeAppointments(); // calculate duration wo checking booking = the earliest finish possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState( Schedule::OBS_Allow ); m_durationForward = duration(cs->earlyStart, use, false); cs->setAllowOverbookingState( obs ); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString() ); #endif } break; } case Node::MustFinishOn: { cs->earlyStart = workTimeAfter( cs->earlyStart ); m_durationForward = duration(cs->earlyStart, use, false); cs->earlyFinish = cs->earlyStart + m_durationForward; //debugPlan<<"MustFinishOn:"<earlyStart<earlyFinish; if (cs->earlyFinish > m_constraintEndTime) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } cs->earlyFinish = qMax( cs->earlyFinish, m_constraintEndTime ); if ( !cs->allowOverbooking() ) { cs->endTime = cs->earlyFinish; cs->startTime = cs->earlyFinish - duration( cs->earlyFinish, use, true ); makeAppointments(); } m_earlyFinish = cs->earlyFinish; m_durationForward = m_earlyFinish - cs->earlyStart; break; } case Node::FinishNotLater: { m_durationForward = duration(cs->earlyStart, use, false); cs->earlyFinish = cs->earlyStart + m_durationForward; //debugPlan<<"FinishNotLater:"<earlyStart<earlyFinish; if (cs->earlyFinish > m_constraintEndTime) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } if ( !cs->allowOverbooking() ) { cs->startTime = cs->earlyStart; cs->endTime = cs->earlyFinish; makeAppointments(); } m_earlyFinish = cs->earlyStart + m_durationForward; break; } case Node::MustStartOn: case Node::StartNotEarlier: { //debugPlan<<"MSO/SNE:"<earlyStart; cs->logDebug( constraintToString() + ": " + m_constraintStartTime.toString() + ' ' + cs->earlyStart.toString() ); cs->earlyStart = workTimeAfter( qMax( cs->earlyStart, m_constraintStartTime ) ); if ( cs->earlyStart < m_constraintStartTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } m_durationForward = duration( cs->earlyStart, use, false ); m_earlyFinish = cs->earlyStart + m_durationForward; if ( !cs->allowOverbooking() ) { cs->startTime = cs->earlyStart; cs->endTime = m_earlyFinish; makeAppointments(); // calculate duration wo checking booking = the earliest finish possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState( Schedule::OBS_Allow ); m_durationForward = duration(cs->startTime, use, false); cs->setAllowOverbookingState( obs ); m_earlyFinish = cs->earlyStart + m_durationForward; #ifndef PLAN_NLOGDEBUG cs->logDebug("MSO/SNE earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString() ); #endif } break; } case Node::FixedInterval: { if ( cs->earlyStart > m_constraintStartTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } //cs->earlyStart = m_constraintStartTime; m_durationForward = m_constraintEndTime - m_constraintStartTime; if ( cs->earlyStart < m_constraintStartTime ) { m_durationForward = m_constraintEndTime - cs->earlyStart; } if ( !cs->allowOverbooking() ) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; makeAppointments(); } m_earlyFinish = cs->earlyStart + m_durationForward; break; } } } else if (type() == Node::Type_Milestone) { m_durationForward = Duration::zeroDuration; switch (constraint()) { case Node::MustFinishOn: //debugPlan<<"MustFinishOn:"<earlyStart; //cs->logDebug( QString( "%1: %2, early start: %3" ).arg( constraintToString() ).arg( m_constraintEndTime.toString() ).arg( cs->earlyStart.toString() ) ); if ( cs->earlyStart < m_constraintEndTime ) { m_durationForward = m_constraintEndTime - cs->earlyStart; } if ( cs->earlyStart > m_constraintEndTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::FinishNotLater: //debugPlan<<"FinishNotLater:"<earlyStart; if ( cs->earlyStart > m_constraintEndTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } m_earlyFinish = cs->earlyStart; break; case Node::MustStartOn: //debugPlan<<"MustStartOn:"<earlyStart; if ( cs->earlyStart < m_constraintStartTime ) { m_durationForward = m_constraintStartTime - cs->earlyStart; } if ( cs->earlyStart > m_constraintStartTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::StartNotEarlier: //debugPlan<<"StartNotEarlier:"<earlyStart; if ( cs->earlyStart < m_constraintStartTime ) { m_durationForward = m_constraintStartTime - cs->earlyStart; } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::FixedInterval: m_earlyFinish = cs->earlyStart + m_durationForward; break; default: m_earlyFinish = cs->earlyStart + m_durationForward; break; } //debugPlan<insertForwardNode( this ); cs->earlyFinish = cs->earlyStart + m_durationForward; foreach ( const Appointment *a, cs->appointments( Schedule::CalculateForward ) ) { cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) ); } // clean up temporary usage cs->startTime = DateTime(); cs->endTime = DateTime(); cs->duration = Duration::zeroDuration; cs->logInfo( i18n( "Early finish calculated: %1", locale.toString(cs->earlyFinish, QLocale::ShortFormat) ) ); cs->incProgress(); #ifndef PLAN_NLOGDEBUG cs->logDebug( QStringLiteral( "Finished calculate forward: %1 ms" ).arg( timer.elapsed() ) ); #endif return m_earlyFinish; } DateTime Task::calculateSuccessors(const QList &list, int use) { DateTime time; foreach (Relation *r, list) { if (r->child()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } DateTime t = r->child()->calculateBackward(use); switch (r->type()) { case Relation::StartStart: { // I must start before my successor, so // I can't finish later than it's (starttime-lag) + my duration t -= r->lag(); Schedule::OBState obs = m_currentSchedule->allowOverbookingState(); m_currentSchedule->setAllowOverbookingState( Schedule::OBS_Allow ); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( QStringLiteral("StartStart: get duration to calculate late start") ); #endif t += duration(t, use, false); m_currentSchedule->setAllowOverbookingState( obs ); break; } case Relation::FinishFinish: // My successor cannot finish before me, so // I can't finish later than it's latest finish - lag t = r->child()->lateFinish() - r->lag(); break; default: t -= r->lag(); break; } if (!time.isValid() || t < time) time = t; } //debugPlan<lateStart; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode( Schedule::CalculateBackward ); //cs->lateFinish = projectNode()->constraintEndTime(); // calculate all successors if (!dependChildNodes().isEmpty()) { DateTime time = calculateSuccessors(dependChildNodes(), use); if (time.isValid() && time < cs->lateFinish) { cs->lateFinish = time; } } if (!m_childProxyRelations.isEmpty()) { DateTime time = calculateSuccessors(m_childProxyRelations, use); if (time.isValid() && time < cs->lateFinish) { cs->lateFinish = time; } } m_calculateBackwardRun = true; return calculateLateStart( use ); } DateTime Task::calculateLateStart(int use) { //debugPlan<lateStart; } bool pert = cs->usePert(); cs->setCalculationMode( Schedule::CalculateBackward ); #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug( QStringLiteral( "Start calculate backward: %1 " ).arg( constraintToString( true ) ) ); #endif QLocale locale; cs->logInfo( i18n( "Calculate late start" ) ); cs->logDebug( QStringLiteral( "%1: late finish= %2" ).arg( constraintToString() ).arg( cs->lateFinish.toString() ) ); //debugPlan<lateFinish; cs->lateFinish = workTimeBefore( cs->lateFinish ); m_durationBackward = duration(cs->lateFinish, use, true); cs->lateStart = cs->lateFinish - m_durationBackward; #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + cs->lateStart.toString() ); #endif if ( !cs->allowOverbooking() ) { cs->startTime = cs->lateStart; cs->endTime = cs->lateFinish; makeAppointments(); // calculate wo checking bookings = latest start possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState( Schedule::OBS_Allow ); m_durationBackward = duration(cs->lateFinish, use, true); cs->setAllowOverbookingState( obs ); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP latest start possible: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + (cs->lateFinish-m_durationBackward).toString() ); #endif } break; case Node::MustStartOn: case Node::StartNotEarlier: { //debugPlan<<"MustStartOn:"<lateFinish; cs->lateFinish = workTimeBefore( cs->lateFinish ); m_durationBackward = duration(cs->lateFinish, use, true); cs->lateStart = cs->lateFinish - m_durationBackward; if ( cs->lateStart < m_constraintStartTime) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } else { cs->lateStart = qMax( cs->earlyStart, m_constraintStartTime ); } if ( !cs->allowOverbooking() ) { if ( constraint() == MustStartOn ) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintStartTime + duration( m_constraintStartTime, use, false ); } else { cs->startTime = qMax( cs->lateStart, m_constraintStartTime ); cs->endTime = qMax( cs->lateFinish, cs->startTime ); // safety } makeAppointments(); } cs->lateStart = cs->lateFinish - m_durationBackward; break; } case Node::MustFinishOn: case Node::FinishNotLater: //debugPlan<<"MustFinishOn:"<lateFinish; cs->lateFinish = workTimeBefore( cs->lateFinish ); cs->endTime = cs->lateFinish; if ( cs->lateFinish < m_constraintEndTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } else { cs->endTime = qMax( cs->earlyFinish, m_constraintEndTime ); } m_durationBackward = duration(cs->endTime, use, true); cs->startTime = cs->endTime - m_durationBackward; if ( !cs->allowOverbooking() ) { makeAppointments(); } m_durationBackward = cs->lateFinish - cs->startTime; cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::FixedInterval: { //cs->lateFinish = m_constraintEndTime; if ( cs->lateFinish < m_constraintEndTime ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } m_durationBackward = m_constraintEndTime - m_constraintStartTime; if ( cs->lateFinish > m_constraintEndTime ) { m_durationBackward = cs->lateFinish - m_constraintStartTime; } if ( !cs->allowOverbooking() ) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; makeAppointments(); } cs->lateStart = cs->lateFinish - m_durationBackward; break; } } } else if (type() == Node::Type_Milestone) { m_durationBackward = Duration::zeroDuration; switch (constraint()) { case Node::MustFinishOn: //debugPlan<<"MustFinishOn:"<lateFinish; if ( m_constraintEndTime < cs->lateFinish ) { m_durationBackward = cs->lateFinish - m_constraintEndTime; } else if ( m_constraintEndTime > cs->lateFinish ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::FinishNotLater: //debugPlan<<"FinishNotLater:"<lateFinish; if ( m_constraintEndTime < cs->lateFinish ) { m_durationBackward = cs->lateFinish - m_constraintEndTime; } else if ( m_constraintEndTime > cs->lateFinish ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::MustStartOn: //debugPlan<<"MustStartOn:"<lateFinish; if ( m_constraintStartTime < cs->lateFinish ) { m_durationBackward = cs->lateFinish - m_constraintStartTime; } else if ( m_constraintStartTime > cs->lateFinish ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } cs->lateStart = cs->lateFinish - m_durationBackward; //cs->logDebug( QString( "%1: constraint:%2, start=%3, finish=%4" ).arg( constraintToString() ).arg( m_constraintStartTime.toString() ).arg( cs->lateStart.toString() ).arg( cs->lateFinish.toString() ) ); break; case Node::StartNotEarlier: //debugPlan<<"MustStartOn:"<lateFinish; if ( m_constraintStartTime > cs->lateFinish ) { cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) ); } cs->lateStart = cs->lateFinish; break; case Node::FixedInterval: cs->lateStart = cs->lateFinish - m_durationBackward; break; default: cs->lateStart = cs->lateFinish - m_durationBackward; break; } //debugPlan<lateFinish; } else if (type() == Node::Type_Summarytask) { warnPlan<<"Summarytasks should not be calculated here: "<insertBackwardNode( this ); cs->lateStart = cs->lateFinish - m_durationBackward; foreach ( const Appointment *a, cs->appointments( Schedule::CalculateBackward ) ) { cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) ); } // clean up temporary usage cs->startTime = DateTime(); cs->endTime = DateTime(); cs->duration = Duration::zeroDuration; cs->logInfo( i18n( "Late start calculated: %1", locale.toString(cs->lateStart, QLocale::ShortFormat) ) ); cs->incProgress(); #ifndef PLAN_NLOGDEBUG cs->logDebug( QStringLiteral( "Finished calculate backward: %1 ms" ).arg( timer.elapsed() ) ); #endif return cs->lateStart; } DateTime Task::schedulePredeccessors(const QList &list, int use) { DateTime time; foreach (Relation *r, list) { if (r->parent()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } // schedule the predecessors DateTime earliest = r->parent()->earlyStart(); DateTime t = r->parent()->scheduleForward(earliest, use); switch (r->type()) { case Relation::StartStart: // I can't start before my predesseccor t = r->parent()->startTime() + r->lag(); break; case Relation::FinishFinish: // I can't end before my predecessor, so // I can't start before it's endtime - my duration #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( QStringLiteral("FinishFinish: get duration to calculate earliest start") ); #endif t -= duration(t + r->lag(), use, true); break; default: t += r->lag(); break; } if (!time.isValid() || t > time) time = t; } //debugPlan<endTime; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; //cs->logDebug( QString( "Schedule forward (early start: %1)" ).arg( cs->earlyStart.toString() ) ); cs->setCalculationMode( Schedule::Scheduling ); DateTime startTime = earliest > cs->earlyStart ? earliest : cs->earlyStart; // First, calculate all my own predecessors DateTime time = schedulePredeccessors(dependParentNodes(), use); if ( time > startTime ) { startTime = time; //debugPlan<earlyStart.toString() ) ); cs->startTime = cs->earlyStart; } QTime timer; timer.start(); cs->logInfo( i18n( "Start schedule forward: %1 ", constraintToString( true ) ) ); QLocale locale; cs->logInfo( i18n( "Schedule from start %1", locale.toString(cs->startTime, QLocale::ShortFormat) ) ); //debugPlan<startTime<<"earliest:"<earlyStart; if ( false/*useCalculateForwardAppointments*/ && m_estimate->type() == Estimate::Type_Effort && ! cs->allowOverbooking() && cs->hasAppointments( Schedule::CalculateForward ) ) { #ifndef PLAN_NLOGDEBUG cs->logDebug( "ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString() ); #endif cs->copyAppointments( Schedule::CalculateForward, Schedule::Scheduling ); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); } cs->startTime = cs->appointmentStartTime(); cs->endTime = cs->appointmentEndTime(); Q_ASSERT( cs->startTime.isValid() ); Q_ASSERT( cs->endTime.isValid() ); cs->duration = cs->endTime - cs->startTime; if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) ); return cs->endTime; } cs->startTime = workTimeAfter( cs->startTime, cs ); #ifndef PLAN_NLOGDEBUG cs->logDebug( "ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString() ); #endif cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } break; case Node::ALAP: // cs->startTime calculated above //debugPlan<startTime<endTime<<" latest="<lateFinish; cs->endTime = workTimeBefore( cs->lateFinish, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; //debugPlan<endTime = workTimeBefore( cs->earlyFinish, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; makeAppointments(); } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if ( cs->recalculate() && completion().isStarted() ) { cs->earlyStart = cs->startTime = completion().startTime(); } break; case Node::StartNotEarlier: // cs->startTime calculated above //debugPlan<<"StartNotEarlier:"<startTime<lateStart; cs->startTime = workTimeAfter( qMax( cs->startTime, m_constraintStartTime ), cs ); cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->startTime < m_constraintStartTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } break; case Node::FinishNotLater: // cs->startTime calculated above //debugPlan<<"FinishNotLater:"<startTime; cs->startTime = workTimeAfter( cs->startTime, cs ); cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->endTime > m_constraintEndTime) { //warnPlan<<"cs->endTime > m_constraintEndTime"; cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } break; case Node::MustStartOn: // Always try to put it on time cs->startTime = workTimeAfter( m_constraintStartTime, cs ); //debugPlan<<"MustStartOn="<startTime; cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; #ifndef PLAN_NLOGDEBUG cs->logDebug( QStringLiteral( "%1: Schedule from %2 to %3" ).arg( constraintToString() ).arg( cs->startTime.toString() ).arg( cs->endTime.toString() ) ); #endif makeAppointments(); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (m_constraintStartTime < cs->startTime ) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } break; case Node::MustFinishOn: // Just try to schedule on time cs->endTime = workTimeBefore( m_constraintEndTime, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; //debugPlan<<"MustFinishOn:"<lateFinish<<":"<startTime<endTime; makeAppointments(); if ( cs->recalculate() && completion().isStarted() ) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if ( cs->endTime != m_constraintEndTime ) { cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } break; case Node::FixedInterval: { // cs->startTime calculated above //debugPlan<<"FixedInterval="<startTime; cs->duration = m_constraintEndTime - m_constraintStartTime; if ( m_constraintStartTime >= cs->earlyStart ) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; } else { cs->startTime = cs->earlyStart; cs->endTime = cs->startTime + cs->duration; cs->constraintError = true; } if ( m_constraintStartTime < cs->startTime ) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } cs->workStartTime = m_constraintStartTime; cs->workEndTime = m_constraintEndTime; //debugPlan<<"FixedInterval="<startTime<<","<endTime; makeAppointments(); break; } default: break; } if ( m_estimate->type() == Estimate::Type_Effort ) { // HACK scheduling may accept deviation less than 5 mins to improve performance cs->effortNotMet = ( m_estimate->value( use, cs->usePert() ) - cs->plannedEffort() ) > ( 5 * 60000 ); if ( cs->effortNotMet ) { cs->logError( i18n( "Effort not met. Estimate: %1, planned: %2", estimate()->value( use, cs->usePert() ).toHours(), cs->plannedEffort().toHours() ) ); } } } else if (type() == Node::Type_Milestone) { if ( cs->recalculate() && completion().isFinished() ) { cs->startTime = completion().startTime(); cs->endTime = completion().finishTime(); m_visitedForward = true; return cs->endTime; } switch (m_constraint) { case Node::ASAP: { cs->endTime = cs->startTime; // TODO check, do we need to check succeccors earliestStart? cs->positiveFloat = cs->lateFinish - cs->endTime; break; } case Node::ALAP: { cs->startTime = qMax( cs->lateFinish, cs->earlyFinish ); cs->endTime = cs->startTime; cs->positiveFloat = Duration::zeroDuration; break; } case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: { //debugPlan<<"MustStartOn:"<startTime; DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime; #ifndef PLAN_NLOGDEBUG cs->logDebug( QStringLiteral( "%1: constraint time=%2, start time=%3" ).arg( constraintToString() ).arg( contime.toString() ).arg( cs->startTime.toString() ) ); #endif if ( cs->startTime < contime ) { if ( contime <= cs->lateFinish || contime <= cs->earlyFinish ) { cs->startTime = contime; } } cs->negativeFloat = cs->startTime > contime ? cs->startTime - contime : contime - cs->startTime; if ( cs->negativeFloat != 0 ) { cs->constraintError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->endTime = cs->startTime; if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; } case Node::StartNotEarlier: if ( cs->startTime < m_constraintStartTime ) { if ( m_constraintStartTime <= cs->lateFinish || m_constraintStartTime <= cs->earlyFinish ) { cs->startTime = m_constraintStartTime; } } if ( cs->startTime < m_constraintStartTime ) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->endTime = cs->startTime; if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; case Node::FinishNotLater: //debugPlan<startTime; if (cs->startTime > m_constraintEndTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintEndTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->endTime = cs->startTime; if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; default: break; } cs->duration = Duration::zeroDuration; //debugPlan<startTime<<","<endTime; } else if (type() == Node::Type_Summarytask) { //shouldn't come here cs->endTime = cs->startTime; cs->duration = cs->endTime - cs->startTime; warnPlan<<"Summarytasks should not be calculated here: "<startTime<<" :"<endTime<<""<startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime() ) { cs->logError( i18n( "Failed to schedule within project target time" ) ); } foreach ( const Appointment *a, cs->appointments() ) { cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) ); } if ( cs->startTime < cs->earlyStart ) { cs->logWarning( i18n( "Starting earlier than early start" ) ); } if ( cs->endTime > cs->lateFinish ) { cs->logWarning( i18n( "Finishing later than late finish" ) ); } cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) ); m_visitedForward = true; cs->incProgress(); m_requests.resetDynamicAllocations(); cs->logInfo( i18n( "Finished schedule forward: %1 ms", timer.elapsed() ) ); return cs->endTime; } DateTime Task::scheduleSuccessors(const QList &list, int use) { DateTime time; foreach (Relation *r, list) { if (r->child()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<child()->name(); continue; } // get the successors starttime DateTime latest = r->child()->lateFinish(); DateTime t = r->child()->scheduleBackward(latest, use); switch (r->type()) { case Relation::StartStart: // I can't start before my successor, so // I can't finish later than it's starttime + my duration #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( QStringLiteral("StartStart: get duration to calculate late finish") ); #endif t += duration(t - r->lag(), use, false); break; case Relation::FinishFinish: t = r->child()->endTime() - r->lag(); break; default: t -= r->lag(); break; } if (!time.isValid() || t < time) time = t; } return time; } DateTime Task::scheduleBackward(const DateTime &latest, int use) { if ( m_scheduleBackwardRun ) { return m_currentSchedule->startTime; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode( Schedule::Scheduling ); DateTime endTime = latest < cs->lateFinish ? latest : cs->lateFinish; // First, calculate all my own successors DateTime time = scheduleSuccessors(dependChildNodes(), use); if (time.isValid() && time < endTime) { endTime = time; } // Then my parents time = scheduleSuccessors(m_childProxyRelations, use); if (time.isValid() && time < endTime) { endTime = time; } if ( ! m_visitedBackward ) { cs->endTime = endTime; } m_scheduleBackwardRun = true; return scheduleFromEndTime( use ); } DateTime Task::scheduleFromEndTime(int use) { //debugPlan<setCalculationMode( Schedule::Scheduling ); bool pert = cs->usePert(); if (m_visitedBackward) { return cs->startTime; } cs->notScheduled = false; if ( !cs->endTime.isValid() ) { cs->endTime = cs->lateFinish; } #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug( QStringLiteral( "Start schedule backward: %1 " ).arg( constraintToString( true ) ) ); #endif QLocale locale; cs->logInfo( i18n( "Schedule from end time: %1", cs->endTime.toString() ) ); if (type() == Node::Type_Task) { cs->duration = m_estimate->value(use, pert); switch (m_constraint) { case Node::ASAP: { // cs->endTime calculated above //debugPlan<duration = duration( cs->endTime, use, true ); e = cs->endTime; cs->startTime = e - cs->duration; } if ( e > cs->lateFinish ) { cs->schedulingError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to schedule within late finish.", constraintToString() ) ); #ifndef PLAN_NLOGDEBUG cs->logDebug( "ASAP: late finish=" + cs->lateFinish.toString() + " end time=" + e.toString() ); #endif } else if ( e > cs->endTime ) { cs->schedulingError = true; cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to schedule within successors start time", constraintToString() ) ); #ifndef PLAN_NLOGDEBUG cs->logDebug( "ASAP: succ. start=" + cs->endTime.toString() + " end time=" + e.toString() ); #endif } if ( cs->lateFinish > e ) { DateTime w = workTimeBefore( cs->lateFinish ); if ( w > e ) { cs->positiveFloat = w - e; } #ifndef PLAN_NLOGDEBUG cs->logDebug( "ASAP: positiveFloat=" + cs->positiveFloat.toString() ); #endif } cs->endTime = e; makeAppointments(); break; } case Node::ALAP: { // cs->endTime calculated above //debugPlan<logDebug( "ALAP: earlyStart=" + cs->earlyStart.toString() + " cs->startTime=" + cs->startTime.toString() ); #endif } else if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; #ifndef PLAN_NLOGDEBUG cs->logDebug( "ALAP: positiveFloat=" + cs->positiveFloat.toString() ); #endif } //debugPlan<endTime; cs->endTime = workTimeBefore( cs->endTime, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if ( cs->startTime < m_constraintStartTime ) { //warnPlan<<"m_constraintStartTime > cs->lateStart"; cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } makeAppointments(); if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } break; case Node::FinishNotLater: // cs->endTime calculated above //debugPlan<<"FinishNotLater:"<endTime; if (cs->endTime > m_constraintEndTime) { cs->endTime = qMax( qMin( m_constraintEndTime, cs->lateFinish ), cs->earlyFinish ); } cs->endTime = workTimeBefore( cs->endTime, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if ( cs->endTime > m_constraintEndTime ) { cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->constraintError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } makeAppointments(); if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } break; case Node::MustStartOn: // Just try to schedule on time //debugPlan<<"MustStartOn="<startTime.toString(); cs->startTime = workTimeAfter( m_constraintStartTime, cs ); cs->duration = duration(cs->startTime, use, false); if ( cs->endTime >= cs->startTime + cs->duration ) { cs->endTime = cs->startTime + cs->duration; } else { cs->endTime = workTimeBefore( cs->endTime ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; } if (m_constraintStartTime != cs->startTime) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } makeAppointments(); if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } break; case Node::MustFinishOn: // Just try to schedule on time //debugPlan<endTime<earlyFinish; cs->endTime = workTimeBefore( m_constraintEndTime, cs ); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if (m_constraintEndTime != cs->endTime ) { cs->negativeFloat = m_constraintEndTime - cs->endTime; cs->constraintError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); //warnPlan<<"m_constraintEndTime > cs->endTime"; } makeAppointments(); if ( cs->lateFinish > cs->endTime ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } break; case Node::FixedInterval: { // cs->endTime calculated above //debugPlan<endTime; cs->duration = m_constraintEndTime - m_constraintStartTime; if ( cs->endTime > m_constraintEndTime ) { cs->endTime = qMax( m_constraintEndTime, cs->earlyFinish ); } cs->startTime = cs->endTime - cs->duration; if (m_constraintEndTime != cs->endTime) { cs->negativeFloat = m_constraintEndTime - cs->endTime; cs->constraintError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->workStartTime = workTimeAfter( cs->startTime ); cs->workEndTime = workTimeBefore( cs->endTime ); makeAppointments(); if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime; } break; } default: break; } m_requests.reserve(cs->startTime, cs->duration); if ( m_estimate->type() == Estimate::Type_Effort ) { // HACK scheduling may accept deviation less than 5 mins to improve performance cs->effortNotMet = ( m_estimate->value( use, cs->usePert() ) - cs->plannedEffort() ) > ( 5 * 60000 ); if ( cs->effortNotMet ) { cs->logError( i18n( "Effort not met. Estimate: %1, planned: %2", estimate()->value( use, cs->usePert() ).toHours(), cs->plannedEffort().toHours() ) ); } } } else if (type() == Node::Type_Milestone) { switch (m_constraint) { case Node::ASAP: if ( cs->endTime < cs->earlyStart ) { cs->schedulingError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to schedule after early start.", constraintToString() ) ); cs->endTime = cs->earlyStart; } else { cs->positiveFloat = cs->lateFinish - cs->endTime; } //cs->endTime = cs->earlyStart; FIXME need to follow predeccessors. Defer scheduling? cs->startTime = cs->endTime; break; case Node::ALAP: cs->startTime = cs->endTime; cs->positiveFloat = cs->lateFinish - cs->endTime; break; case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: { DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime; if ( contime < cs->earlyStart ) { if ( cs->earlyStart < cs->endTime ) { cs->endTime = cs->earlyStart; } } else if ( contime < cs->endTime ) { cs->endTime = contime; } cs->negativeFloat = cs->endTime > contime ? cs->endTime - contime : contime - cs->endTime; if ( cs->negativeFloat != 0 ) { cs->constraintError = true; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->startTime = cs->endTime; if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; } case Node::StartNotEarlier: cs->startTime = cs->endTime; if ( m_constraintStartTime > cs->startTime) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; case Node::FinishNotLater: if ( m_constraintEndTime < cs->earlyStart ) { if ( cs->earlyStart < cs->endTime ) { cs->endTime = cs->earlyStart; } } else if ( m_constraintEndTime < cs->endTime ) { cs->endTime = m_constraintEndTime; } if ( m_constraintEndTime > cs->endTime ) { cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) ); } cs->startTime = cs->endTime; if ( cs->negativeFloat == Duration::zeroDuration ) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; default: break; } cs->duration = Duration::zeroDuration; } else if (type() == Node::Type_Summarytask) { //shouldn't come here cs->startTime = cs->endTime; cs->duration = cs->endTime - cs->startTime; warnPlan<<"Summarytasks should not be calculated here: "<startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime() ) { cs->logError( i18n( "Failed to schedule within project target time" ) ); } foreach ( const Appointment *a, cs->appointments() ) { cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat ), locale.toString(a->endTime(), QLocale::ShortFormat) ) ); } if ( cs->startTime < cs->earlyStart ) { cs->logWarning( i18n( "Starting earlier than early start" ) ); } if ( cs->endTime > cs->lateFinish ) { cs->logWarning( i18n( "Finishing later than late finish" ) ); } cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) ); m_visitedBackward = true; cs->incProgress(); m_requests.resetDynamicAllocations(); #ifndef PLAN_NLOGDEBUG cs->logDebug( QStringLiteral( "Finished schedule backward: %1 ms" ).arg( timer.elapsed() ) ); #endif return cs->startTime; } void Task::adjustSummarytask() { if (m_currentSchedule == 0) return; if (type() == Type_Summarytask) { DateTime start = m_currentSchedule->lateFinish; DateTime end = m_currentSchedule->earlyStart; foreach (Node *n, m_nodes) { n->adjustSummarytask(); if (n->startTime() < start) start = n->startTime(); if (n->endTime() > end) end = n->endTime(); } m_currentSchedule->startTime = start; m_currentSchedule->endTime = end; m_currentSchedule->duration = end - start; m_currentSchedule->notScheduled = false; //debugPlan<name<<":"<startTime.toString()<<" :"<endTime.toString(); } } Duration Task::duration(const DateTime &time, int use, bool backward) { //debugPlan; // TODO: handle risc if (m_currentSchedule == 0) { errorPlan<<"No current schedule"; return Duration::zeroDuration; } if (!time.isValid()) { #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( QStringLiteral("Calculate duration: Start time is not valid") ); #endif return Duration::zeroDuration; } //debugPlan< calcDuration"<<(backward?"(B)":"(F)")<resourceNotAvailable = true; dur = effort; //??? } return dur; } if (m_estimate->type() == Estimate::Type_Duration) { return length( time, dur, backward ); } errorPlan<<"Unsupported estimate type: "<type(); return dur; } Duration Task::length(const DateTime &time, KPlato::Duration duration, bool backward) { return length( time, duration, m_currentSchedule, backward ); } Duration Task::length(const DateTime &time, KPlato::Duration duration, Schedule *sch, bool backward) { //debugPlan<<"--->"<<(backward?"(B)":"(F)")<logDebug( QStringLiteral("Calculate length: estimate == 0") ); #else Q_UNUSED(sch) #endif return l; } Calendar *cal = m_estimate->calendar(); if ( cal == 0) { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Calculate length: No calendar, return estimate " + duration.toString() ); #endif return duration; } #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Calculate length from: " + time.toString() ); #endif DateTime logtime = time; bool sts=true; bool match = false; DateTime start = time; int inc = backward ? -1 : 1; DateTime end = start; Duration l1; int nDays = backward ? projectNode()->constraintStartTime().daysTo( time ) : time.daysTo( projectNode()->constraintEndTime() ); for (int i=0; !match && i <= nDays; ++i) { // days end = end.addDays(inc); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); //debugPlan<<"["<logDebug( "Days: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')' ); #endif logtime = start; for (int i=0; !match && i < 24; ++i) { // hours end = end.addSecs(inc*60*60); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else { end = start; break; } //debugPlan<<"duration(h)["<effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else if (l + l1 > duration) { end = start; break; } //debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<" time="<effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else if (l + l1 > duration) { end = start; break; } //debugPlan<<"duration(s)["<logDebug( "Seconds: duration " + logtime.toString() + " - " + end.toString() + " l " + l.toString() + " (" + (duration - l).toString() + ')' ); #endif for (int i=0; !match && i < 1000; ++i) { //milliseconds end.setTime(end.time().addMSecs(inc)); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Got more than asked for, should not happen! Want: " + duration.toString(Duration::Format_Hour) + " got: " + l.toString(Duration::Format_Hour) ); #endif break; } //debugPlan<<"duration(ms)["<logError( i18n( "Could not match work duration. Want: %1 got: %2", l.toString( Duration::Format_i18nHour ), duration.toString( Duration::Format_i18nHour ) ) ); } DateTime t = end; if (l != Duration::zeroDuration) { if ( backward ) { if ( end < projectNode()->constraintEndTime() ) { t = cal->firstAvailableAfter(end, projectNode()->constraintEndTime()); } } else { if ( end > projectNode()->constraintStartTime() ) { t = cal->firstAvailableBefore(end, projectNode()->constraintStartTime()); } } #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Moved end to work: " + end.toString() + " -> " + t.toString() ); #endif } end = t.isValid() ? t : time; //debugPlan<<"<---"<<(backward?"(B)":"(F)")<time ? end-time : time-end; if ( match ) { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Calculated length: " + time.toString() + " - " + end.toString() + " = " + l.toString() ); #endif } return l; } void Task::clearProxyRelations() { m_parentProxyRelations.clear(); m_childProxyRelations.clear(); } void Task::addParentProxyRelations( const QList &list ) { //debugPlan<addParentProxyRelations(list); n->addParentProxyRelations(dependParentNodes()); } } else { // add 'this' as child relation to the relations parent //debugPlan<parent()->addChildProxyRelation(this, r); // add a parent relation to myself addParentProxyRelation(r->parent(), r); } } } void Task::addChildProxyRelations( const QList &list) { //debugPlan<addChildProxyRelations(list); n->addChildProxyRelations(dependChildNodes()); } } else { // add 'this' as parent relation to the relations child //debugPlan<child()->addParentProxyRelation(this, r); // add a child relation to myself addChildProxyRelation(r->child(), r); } } } void Task::addParentProxyRelation(Node *node, const Relation *rel) { if (node->type() != Type_Summarytask) { if (type() == Type_Summarytask) { //debugPlan<<"Add parent proxy from my children"<name(); foreach (Node *n, m_nodes) { n->addParentProxyRelation(node, rel); } } else { //debugPlan<<"Add parent proxy from"<name()<<" to (me)"<type(), rel->lag())); } } } void Task::addChildProxyRelation(Node *node, const Relation *rel) { if (node->type() != Type_Summarytask) { if (type() == Type_Summarytask) { //debugPlan<<"Add child proxy from my children"<name(); foreach (Node *n, m_nodes) { n->addChildProxyRelation(node, rel); } } else { //debugPlan<<"Add child proxy from (me)"<name(); m_childProxyRelations.append(new ProxyRelation(this, node, rel->type(), rel->lag())); } } } bool Task::isEndNode() const { return m_dependChildNodes.isEmpty() && m_childProxyRelations.isEmpty(); } bool Task::isStartNode() const { return m_dependParentNodes.isEmpty() && m_parentProxyRelations.isEmpty(); } DateTime Task::workTimeAfter(const DateTime &dt, Schedule *sch) const { DateTime t; if ( m_estimate->type() == Estimate::Type_Duration ) { if ( m_estimate->calendar() ) { t = m_estimate->calendar()->firstAvailableAfter( dt, projectNode()->constraintEndTime() ); } } else { t = m_requests.workTimeAfter(dt, sch); #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( QStringLiteral( "workTimeAfter: %1 = %2" ).arg( dt.toString() ).arg( t.toString() ) ); #endif } return t.isValid() ? t : dt; } DateTime Task::workTimeBefore(const DateTime &dt, Schedule *sch) const { DateTime t; if ( m_estimate->type() == Estimate::Type_Duration ) { if ( m_estimate->calendar() ) { t = m_estimate->calendar()->firstAvailableBefore( dt, projectNode()->constraintStartTime() ); } } else { t = m_requests.workTimeBefore(dt, sch); } return t.isValid() ? t : dt; } Duration Task::positiveFloat( long id ) const { Schedule *s = schedule( id ); return s == 0 ? Duration::zeroDuration : s->positiveFloat; } void Task::setPositiveFloat( KPlato::Duration fl, long id ) const { Schedule *s = schedule( id ); if ( s ) s->positiveFloat = fl; } Duration Task::negativeFloat( long id ) const { Schedule *s = schedule( id ); return s == 0 ? Duration::zeroDuration : s->negativeFloat; } void Task::setNegativeFloat( KPlato::Duration fl, long id ) const { Schedule *s = schedule( id ); if ( s ) s->negativeFloat = fl; } Duration Task::freeFloat( long id ) const { Schedule *s = schedule( id ); return s == 0 ? Duration::zeroDuration : s->freeFloat; } void Task::setFreeFloat( KPlato::Duration fl, long id ) const { Schedule *s = schedule( id ); if ( s ) s->freeFloat = fl; } Duration Task::startFloat( long id ) const { Schedule *s = schedule( id ); return s == 0 || s->earlyStart > s->lateStart ? Duration::zeroDuration : ( s->earlyStart - s->lateStart ); } Duration Task::finishFloat( long id ) const { Schedule *s = schedule( id ); return s == 0 || s->lateFinish < s->earlyFinish ? Duration::zeroDuration : ( s->lateFinish - s->earlyFinish ); } bool Task::isCritical( long id ) const { Schedule *s = schedule( id ); return s == 0 ? false : s->isCritical(); } bool Task::calcCriticalPath(bool fromEnd) { if (m_currentSchedule == 0) return false; //debugPlan<child()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } foreach (Relation *r, m_dependChildNodes) { if (r->child()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } } else { if (isStartNode() && startFloat() == 0 && finishFloat() == 0) { m_currentSchedule->inCriticalPath = true; //debugPlan<parent()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } foreach (Relation *r, m_dependParentNodes) { if (r->parent()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } } //debugPlan<freeFloat.toString(); } } void Task::setCurrentSchedule(long id) { setCurrentSchedulePtr(findSchedule(id)); Node::setCurrentSchedule(id); } bool Task::effortMetError( long id ) const { Schedule *s = schedule( id ); if (s == 0 || s->notScheduled || m_estimate->type() != Estimate::Type_Effort) { return false; } return s->effortNotMet; } uint Task::state( long id ) const { int st = Node::State_None; if ( ! isScheduled( id ) ) { st |= State_NotScheduled; } if ( completion().isFinished() ) { st |= Node::State_Finished; if ( completion().finishTime() > endTime( id ) ) { st |= State_FinishedLate; } if ( completion().finishTime() < endTime( id ) ) { st |= State_FinishedEarly; } } else if ( completion().isStarted() ) { st |= Node::State_Started; if ( completion().startTime() > startTime( id ) ) { st |= State_StartedLate; } if ( completion().startTime() < startTime( id ) ) { st |= State_StartedEarly; } if ( completion().percentFinished() > 0 ) { st |= State_Running; } if ( endTime( id ) < QDateTime::currentDateTime() ) { st |= State_Late; } } else if ( isScheduled( id ) ) { if ( startTime( id ) < QDateTime::currentDateTime() ) { st |= State_Late; } } st |= State_ReadyToStart; //TODO: check proxy relations foreach ( const Relation *r, m_dependParentNodes ) { if ( ! static_cast( r->parent() )->completion().isFinished() ) { st &= ~Node::State_ReadyToStart; st |= Node::State_NotReadyToStart; break; } } return st; } void Task::addWorkPackage( WorkPackage *wp ) { emit workPackageToBeAdded( this, m_packageLog.count() ); m_packageLog.append( wp ); emit workPackageAdded( this ); } void Task::removeWorkPackage( WorkPackage *wp ) { int index = m_packageLog.indexOf( wp ); if ( index < 0 ) { return; } emit workPackageToBeRemoved( this, index ); m_packageLog.removeAt( index ); emit workPackageRemoved( this ); } WorkPackage *Task::workPackageAt( int index ) const { Q_ASSERT ( index >= 0 && index < m_packageLog.count() ); return m_packageLog.at( index ); } QString Task::wpOwnerName() const { if ( m_packageLog.isEmpty() ) { return m_workPackage.ownerName(); } return m_packageLog.last()->ownerName(); } WorkPackage::WPTransmitionStatus Task::wpTransmitionStatus() const { if ( m_packageLog.isEmpty() ) { return m_workPackage.transmitionStatus(); } return m_packageLog.last()->transmitionStatus(); } DateTime Task::wpTransmitionTime() const { if ( m_packageLog.isEmpty() ) { return m_workPackage.transmitionTime(); } return m_packageLog.last()->transmitionTime(); } //------------------------------------------ Completion::Completion( Node *node ) : m_node( node ), m_started( false ), m_finished( false ), m_entrymode( EnterCompleted ) {} Completion::Completion( const Completion &c ) { copy( c ); } Completion::~Completion() { qDeleteAll( m_entries ); qDeleteAll( m_usedEffort ); } void Completion::copy( const Completion &p ) { m_node = 0; //NOTE m_started = p.isStarted(); m_finished = p.isFinished(); m_startTime = p.startTime(); m_finishTime = p.finishTime(); m_entrymode = p.entrymode(); qDeleteAll( m_entries ); m_entries.clear(); Completion::EntryList::ConstIterator entriesIt = p.entries().constBegin(); const Completion::EntryList::ConstIterator entriesEnd = p.entries().constEnd(); for (; entriesIt != entriesEnd; ++entriesIt) { addEntry( entriesIt.key(), new Entry( *entriesIt.value() ) ); } qDeleteAll( m_usedEffort ); m_usedEffort.clear(); Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapIt = p.usedEffortMap().constBegin(); const Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapEnd = p.usedEffortMap().constEnd(); for (; usedEffortMapIt != usedEffortMapEnd; ++usedEffortMapIt) { addUsedEffort( usedEffortMapIt.key(), new UsedEffort( *usedEffortMapIt.value() ) ); } } bool Completion::operator==( const Completion &p ) { return m_started == p.isStarted() && m_finished == p.isFinished() && m_startTime == p.startTime() && m_finishTime == p.finishTime() && m_entries == p.entries() && m_usedEffort == p.usedEffortMap(); } Completion &Completion::operator=( const Completion &p ) { copy( p ); return *this; } void Completion::changed( int property) { if ( m_node ) { m_node->changed(property); } } void Completion::setStarted( bool on ) { m_started = on; changed(Node::CompletionStarted); } void Completion::setFinished( bool on ) { m_finished = on; changed(Node::CompletionFinished); } void Completion::setStartTime( const DateTime &dt ) { m_startTime = dt; changed(Node::CompletionStartTime); } void Completion::setFinishTime( const DateTime &dt ) { m_finishTime = dt; changed(Node::CompletionFinishTime); } void Completion::setPercentFinished( QDate date, int value ) { Entry *e = 0; if ( m_entries.contains( date ) ) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->percentFinished = value; changed(Node::CompletionPercentage); } void Completion::setRemainingEffort( QDate date, KPlato::Duration value ) { Entry *e = 0; if ( m_entries.contains( date ) ) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->remainingEffort = value; changed(Node::CompletionRemainingEffort); } void Completion::setActualEffort( QDate date, KPlato::Duration value ) { Entry *e = 0; if ( m_entries.contains( date ) ) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->totalPerformed = value; changed(Node::CompletionActualEffort); } void Completion::addEntry( QDate date, Entry *entry ) { m_entries.insert( date, entry ); //debugPlan<percentFinished; } int Completion::percentFinished( QDate date ) const { int x = 0; EntryList::const_iterator it; for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) { x = it.value()->percentFinished; } return x; } Duration Completion::remainingEffort() const { return m_entries.isEmpty() ? Duration::zeroDuration : m_entries.last()->remainingEffort; } Duration Completion::remainingEffort( QDate date ) const { Duration x; EntryList::const_iterator it; for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) { x = it.value()->remainingEffort; } return x; } Duration Completion::actualEffort() const { Duration eff; if ( m_entrymode == EnterEffortPerResource ) { foreach( const UsedEffort *ue, m_usedEffort ) { const QMap map = ue->actualEffortMap(); QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { eff += it.value().effort(); } } } else if ( ! m_entries.isEmpty() ) { eff = m_entries.last()->totalPerformed; } return eff; } Duration Completion::actualEffort( const Resource *resource, QDate date ) const { UsedEffort *ue = usedEffort( resource ); if ( ue == 0 ) { return Duration::zeroDuration; } UsedEffort::ActualEffort ae = ue->effort( date ); return ae.effort(); } Duration Completion::actualEffort( QDate date ) const { Duration eff; if ( m_entrymode == EnterEffortPerResource ) { foreach( const UsedEffort *ue, m_usedEffort ) { if ( ue && ue->actualEffortMap().contains( date ) ) { eff += ue->actualEffortMap().value( date ).effort(); } } } else { // Hmmm: How to really know a specific date? if ( m_entries.contains( date ) ) { eff = m_entries[ date ]->totalPerformed; } } return eff; } Duration Completion::actualEffortTo( QDate date ) const { //debugPlan<effortTo( date ); } } else { QListIterator it( m_entries.uniqueKeys() ); it.toBack(); while ( it.hasPrevious() ) { QDate d = it.previous(); if ( d <= date ) { eff = m_entries[ d ]->totalPerformed; break; } } } return eff; } double Completion::averageCostPrHour( QDate date, long id ) const { Schedule *s = m_node->schedule( id ); if ( s == 0 ) { return 0.0; } double cost = 0.0; double eff = 0.0; QList cl; foreach ( const Appointment *a, s->appointments() ) { cl << a->resource()->resource()->normalRate(); double e = a->plannedEffort( date ).toDouble( Duration::Unit_h ); if ( e > 0.0 ) { eff += e; cost += e * cl.last(); } } if ( eff > 0.0 ) { cost /= eff; } else { foreach ( double c, cl ) { cost += c; } cost /= cl.count(); } return cost; } EffortCostMap Completion::effortCostPrDay(QDate start, QDate end, long id ) const { //debugPlan<name()< et ) { break; } Duration e = m_entries[ d ]->totalPerformed; if ( e != Duration::zeroDuration && e != last ) { Duration eff = e - last; ec.insert( d, eff, eff.toDouble( Duration::Unit_h ) * averageCostPrHour( d, id ) ); last = e; } } break; } case EnterEffortPerResource: { std::pair dates = actualStartEndDates(); if ( ! dates.first.isValid() ) { // no data, so just break break; } QDate st = start.isValid() ? start : dates.first; QDate et = end.isValid() ? end : dates.second; for ( QDate d = st; d <= et; d = d.addDays( 1 ) ) { ec.add( d, actualEffort( d ), actualCost( d ) ); } break; } } return ec; } EffortCostMap Completion::effortCostPrDay(const Resource *resource, QDate start, QDate end, long id ) const { Q_UNUSED(id); //debugPlan<name()< dates = actualStartEndDates(); if ( ! dates.first.isValid() ) { // no data, so just break break; } QDate st = start.isValid() ? start : dates.first; QDate et = end.isValid() ? end : dates.second; for ( QDate d = st; d <= et; d = d.addDays( 1 ) ) { ec.add( d, actualEffort( resource, d ), actualCost( resource, d ) ); } break; } } return ec; } void Completion::addUsedEffort( const Resource *resource, Completion::UsedEffort *value ) { UsedEffort *v = value == 0 ? new UsedEffort() : value; if ( m_usedEffort.contains( resource ) ) { m_usedEffort[ resource ]->mergeEffort( *v ); delete v; } else { m_usedEffort.insert( resource, v ); } changed(Node::CompletionUsedEffort); } QString Completion::note() const { return m_entries.isEmpty() ? QString() : m_entries.last()->note; } void Completion::setNote( const QString &str ) { if ( ! m_entries.isEmpty() ) { m_entries.last()->note = str; changed(); } } std::pair Completion::actualStartEndDates() const { std::pair p; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { if (!it.value()->actualEffortMap().isEmpty()) { QDate d = it.value()->firstDate(); if (!p.first.isValid() || d < p.first) { p.first = d; } d = it.value()->lastDate(); if (!p.second.isValid() || d > p.second ) { p.second = d; } } } return p; } double Completion::actualCost( QDate date ) const { //debugPlan<normalRate(); double oc = it.key()->overtimeRate(); if (it.value()->actualEffortMap().contains(date)) { UsedEffort::ActualEffort a = it.value()->effort(date); c += a.normalEffort().toDouble( Duration::Unit_h ) * nc; c += a.overtimeEffort().toDouble( Duration::Unit_h ) * oc; } } return c; } double Completion::actualCost( const Resource *resource ) const { UsedEffort *ue = usedEffort( resource ); if ( ue == 0 ) { return 0.0; } double c = 0.0; double nc = resource->normalRate(); double oc = resource->overtimeRate(); foreach ( const UsedEffort::ActualEffort &a, ue->actualEffortMap() ) { c += a.normalEffort().toDouble( Duration::Unit_h ) * nc; c += a.overtimeEffort().toDouble( Duration::Unit_h ) * oc; } return c; } double Completion::actualCost() const { double c = 0.0; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { c += actualCost(it.key()); } return c; } double Completion::actualCost( const Resource *resource, QDate date ) const { UsedEffort *ue = usedEffort( resource ); if ( ue == 0 ) { return 0.0; } UsedEffort::ActualEffort a = ue->actualEffortMap().value( date ); double c = a.normalEffort().toDouble( Duration::Unit_h ) * resource->normalRate(); c += a.overtimeEffort().toDouble( Duration::Unit_h ) * resource->overtimeRate(); return c; } EffortCostMap Completion::actualEffortCost( long int id, KPlato::EffortCostCalculationType type ) const { //debugPlan; EffortCostMap map; if ( ! isStarted() ) { return map; } QList< QMap > lst; QList< double > rate; QDate start, end; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { const Resource *r = it.key(); //debugPlan<name()<name(); lst << usedEffort(r)->actualEffortMap(); if ( lst.last().isEmpty() ) { lst.takeLast(); continue; } if ( r->type() == Resource::Type_Material ) { if ( type == ECCT_All ) { rate.append( r->normalRate() ); } else if ( type == ECCT_EffortWork ) { rate.append( 0.0 ); } else { lst.takeLast(); continue; } } else { rate.append( r->normalRate() ); } if ( ! start.isValid() || start > lst.last().firstKey() ) { start = lst.last().firstKey(); } if ( ! end.isValid() || end < lst.last().lastKey() ) { end = lst.last().lastKey(); } } if ( ! lst.isEmpty() && start.isValid() && end.isValid() ) { for ( QDate d = start; d <= end; d = d.addDays( 1 ) ) { EffortCost c; for ( int i = 0; i < lst.count(); ++i ) { UsedEffort::ActualEffort a = lst.at( i ).value( d ); double nc = rate.value( i ); Duration eff = a.normalEffort(); double cost = eff.toDouble( Duration::Unit_h ) * nc; c.add( eff, cost ); } if ( c.effort() != Duration::zeroDuration || c.cost() != 0.0 ) { map.add( d, c ); } } } else if ( ! m_entries.isEmpty() ) { QDate st = start.isValid() ? start : m_startTime.date(); QDate et = end.isValid() ? end : m_finishTime.date(); Duration last; foreach ( const QDate &d, m_entries.uniqueKeys() ) { if ( d < st ) { continue; } Duration e = m_entries[ d ]->totalPerformed; if ( e != Duration::zeroDuration && e != last ) { //debugPlan<name()< et ) { break; } } } return map; } EffortCost Completion::actualCostTo( long int id, QDate date ) const { //debugPlan<( m ); } QString Completion::entryModeToString() const { return entrymodeList().value( m_entrymode ); } bool Completion::loadXML( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; QString s; m_started = (bool)element.attribute(QStringLiteral("started"), QStringLiteral("0")).toInt(); m_finished = (bool)element.attribute(QStringLiteral("finished"), QStringLiteral("0")).toInt(); s = element.attribute(QStringLiteral("startTime")); if (!s.isEmpty()) { m_startTime = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute(QStringLiteral("finishTime")); if (!s.isEmpty()) { m_finishTime = DateTime::fromString(s, status.projectTimeZone()); } setEntrymode( element.attribute( QStringLiteral("entrymode") ) ); if (status.version() < QLatin1String("0.6")) { if ( m_started ) { Entry *entry = new Entry( element.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(element.attribute(QStringLiteral("remaining-effort"))), Duration::fromString(element.attribute(QStringLiteral("performed-effort"))) ); entry->note = element.attribute(QStringLiteral("note")); QDate date = m_startTime.date(); if ( m_finished ) { date = m_finishTime.date(); } // almost the best we can do ;) addEntry( date, entry ); } } else { KoXmlElement e; forEachElement(e, element) { if (e.tagName() == QLatin1String("completion-entry")) { QDate date; s = e.attribute(QStringLiteral("date")); if ( !s.isEmpty() ) { date = QDate::fromString( s, Qt::ISODate ); } if ( !date.isValid() ) { warnPlan<<"Invalid date: "<percentFinished ); elm.setAttribute( QStringLiteral("remaining-effort"), e->remainingEffort.toString() ); elm.setAttribute( QStringLiteral("performed-effort"), e->totalPerformed.toString() ); elm.setAttribute( QStringLiteral("note"), e->note ); } if ( ! m_usedEffort.isEmpty() ) { QDomElement elm = el.ownerDocument().createElement(QStringLiteral("used-effort")); el.appendChild(elm); ResourceUsedEffortMap::ConstIterator i = m_usedEffort.constBegin(); for ( ; i != m_usedEffort.constEnd(); ++i ) { if ( i.value() == 0 ) { continue; } QDomElement e = elm.ownerDocument().createElement(QStringLiteral("resource")); elm.appendChild(e); e.setAttribute( QStringLiteral("id"), i.key()->id() ); i.value()->saveXML( e ); } } } //-------------- Completion::UsedEffort::UsedEffort() { } Completion::UsedEffort::UsedEffort( const UsedEffort &e ) { mergeEffort( e ); } Completion::UsedEffort::~UsedEffort() { } void Completion::UsedEffort::mergeEffort( const Completion::UsedEffort &value ) { const QMap map = value.actualEffortMap(); QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { setEffort(it.key(), it.value()); } } void Completion::UsedEffort::setEffort( QDate date, const ActualEffort &value ) { m_actual.insert( date, value ); } Duration Completion::UsedEffort::effortTo( QDate date ) const { Duration eff; QMap::const_iterator it; for (it = m_actual.constBegin(); it != m_actual.constEnd() && it.key() <= date; ++it) { eff += it.value().effort(); } return eff; } Duration Completion::UsedEffort::effort() const { Duration eff; foreach ( const ActualEffort &e, m_actual ) { eff += e.effort(); } return eff; } bool Completion::UsedEffort::operator==( const Completion::UsedEffort &e ) const { return m_actual == e.actualEffortMap(); } bool Completion::UsedEffort::loadXML(KoXmlElement &element, XMLLoaderObject & ) { //debugPlan; KoXmlElement e; forEachElement(e, element) { if (e.tagName() == QLatin1String("actual-effort")) { QDate date = QDate::fromString( e.attribute(QStringLiteral("date")), Qt::ISODate ); if ( date.isValid() ) { ActualEffort a; a.setNormalEffort( Duration::fromString( e.attribute( QStringLiteral("normal-effort") ) ) ); a.setOvertimeEffort( Duration::fromString( e.attribute( QStringLiteral("overtime-effort") ) ) ); setEffort( date, a ); } } } return true; } void Completion::UsedEffort::saveXML(QDomElement &element ) const { if ( m_actual.isEmpty() ) { return; } DateUsedEffortMap::ConstIterator i = m_actual.constBegin(); for ( ; i != m_actual.constEnd(); ++i ) { QDomElement el = element.ownerDocument().createElement(QStringLiteral("actual-effort")); element.appendChild( el ); el.setAttribute( QStringLiteral("overtime-effort"), i.value().overtimeEffort().toString() ); el.setAttribute( QStringLiteral("normal-effort"), i.value().normalEffort().toString() ); el.setAttribute( QStringLiteral("date"), i.key().toString( Qt::ISODate ) ); } } //---------------------------------- WorkPackage::WorkPackage( Task *task ) : m_task( task ), m_manager( 0 ), m_transmitionStatus( TS_None ) { m_completion.setNode( task ); } WorkPackage::WorkPackage( const WorkPackage &wp ) : m_task( 0 ), m_manager( 0 ), m_completion( wp.m_completion ), m_ownerName( wp.m_ownerName ), m_ownerId( wp.m_ownerId ), m_transmitionStatus( wp.m_transmitionStatus ), m_transmitionTime( wp.m_transmitionTime ) { } WorkPackage::~WorkPackage() { } bool WorkPackage::loadXML(KoXmlElement &element, XMLLoaderObject &status ) { Q_UNUSED(status); m_ownerName = element.attribute( QStringLiteral("owner") ); m_ownerId = element.attribute( QStringLiteral("owner-id") ); return true; } void WorkPackage::saveXML(QDomElement &element) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage")); element.appendChild(el); el.setAttribute( QStringLiteral("owner"), m_ownerName ); el.setAttribute( QStringLiteral("owner-id"), m_ownerId ); } bool WorkPackage::loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status ) { m_ownerName = element.attribute( QStringLiteral("owner") ); m_ownerId = element.attribute( QStringLiteral("owner-id") ); m_transmitionStatus = transmitionStatusFromString( element.attribute( QStringLiteral("status") ) ); m_transmitionTime = DateTime( QDateTime::fromString( element.attribute( QStringLiteral("time") ), Qt::ISODate ) ); return m_completion.loadXML( element, status ); } void WorkPackage::saveLoggedXML(QDomElement &element) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage")); element.appendChild(el); el.setAttribute( QStringLiteral("owner"), m_ownerName ); el.setAttribute( QStringLiteral("owner-id"), m_ownerId ); el.setAttribute( QStringLiteral("status"), transmitionStatusToString( m_transmitionStatus ) ); el.setAttribute( QStringLiteral("time"), m_transmitionTime.toString( Qt::ISODate ) ); m_completion.saveXML( el ); } QList WorkPackage::fetchResources() { return fetchResources( id() ); } QList WorkPackage::fetchResources( long id ) { //debugPlan< lst; if ( id == NOTSCHEDULED ) { if ( m_task ) { lst << m_task->requestedResources(); } } else { if ( m_task ) lst = m_task->assignedResources( id ); foreach ( const Resource *r, m_completion.resources() ) { if ( ! lst.contains( const_cast( r ) ) ) { lst << const_cast( r ); } } } return lst; } Completion &WorkPackage::completion() { return m_completion; } const Completion &WorkPackage::completion() const { return m_completion; } void WorkPackage::setScheduleManager( ScheduleManager *sm ) { m_manager = sm; } QString WorkPackage::transmitionStatusToString( WorkPackage::WPTransmitionStatus sts, bool trans ) { QString s = trans ? i18n( "None" ) : QStringLiteral("None"); switch ( sts ) { case TS_Send: s = trans ? i18n( "Send" ) : QStringLiteral("Send"); break; case TS_Receive: s = trans ? i18n( "Receive" ) : QStringLiteral("Receive"); break; default: break; } return s; } WorkPackage::WPTransmitionStatus WorkPackage::transmitionStatusFromString( const QString &sts ) { QStringList lst; lst << QStringLiteral("None") << QStringLiteral("Send") << QStringLiteral("Receive"); int s = lst.indexOf( sts ); return s < 0 ? TS_None : static_cast( s ); } void WorkPackage::clear() { //m_task = 0; m_manager = 0; m_ownerName.clear(); m_ownerId.clear(); m_transmitionStatus = TS_None; m_transmitionTime = DateTime(); m_log.clear(); m_completion = Completion(); m_completion.setNode( m_task ); } //-------------------------------- WorkPackageSettings::WorkPackageSettings() : usedEffort( true ), progress( true ), documents( true ) { } void WorkPackageSettings::saveXML( QDomElement &element ) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("settings")); element.appendChild( el ); el.setAttribute( QStringLiteral("used-effort"), QString::number(usedEffort) ); el.setAttribute( QStringLiteral("progress"), QString::number(progress) ); el.setAttribute( QStringLiteral("documents"), QString::number(documents) ); } bool WorkPackageSettings::loadXML( const KoXmlElement &element ) { usedEffort = (bool)element.attribute( QStringLiteral("used-effort") ).toInt(); progress = (bool)element.attribute( QStringLiteral("progress") ).toInt(); documents = (bool)element.attribute( QStringLiteral("documents") ).toInt(); return true; } bool WorkPackageSettings::operator==( KPlato::WorkPackageSettings s ) const { return usedEffort == s.usedEffort && progress == s.progress && documents == s.documents; } bool WorkPackageSettings::operator!=( KPlato::WorkPackageSettings s ) const { return ! operator==( s ); } } //KPlato namespace #ifndef QT_NO_DEBUG_STREAM QDebug operator<<( QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae ) { dbg << QStringLiteral( "%1" ).arg( ae.normalEffort().toDouble( KPlato::Duration::Unit_h ), 1 ); return dbg; } #endif diff --git a/src/libs/kernel/kpttask.h b/src/libs/kernel/kpttask.h index 96359507..85e01db7 100644 --- a/src/libs/kernel/kpttask.h +++ b/src/libs/kernel/kpttask.h @@ -1,763 +1,766 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004 - 2007 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. */ #ifndef KPTTASK_H #define KPTTASK_H #include "plankernel_export.h" #include "kptnode.h" #include "kptglobal.h" #include "kptdatetime.h" #include "kptduration.h" #include "kptresource.h" #include #include #include /// The main namespace. namespace KPlato { class Completion; class XmlSaveContext; /** * The Completion class holds information about the tasks progress. */ class PLANKERNEL_EXPORT Completion { public: class PLANKERNEL_EXPORT UsedEffort { public: class PLANKERNEL_EXPORT ActualEffort : public std::pair { public: explicit ActualEffort( KPlato::Duration ne = Duration::zeroDuration, KPlato::Duration oe = Duration::zeroDuration ) : std::pair( ne, oe ) {} ActualEffort( const ActualEffort &e ) : std::pair( e.first, e.second ) {} ~ActualEffort() {} Duration normalEffort() const { return first; } void setNormalEffort( KPlato::Duration e ) { first = e; } Duration overtimeEffort() const { return second; } void setOvertimeEffort( KPlato::Duration e ) { second = e; } /// Returns the sum of normalEffort + overtimeEffort Duration effort() const { return first + second; } void setEffort( KPlato::Duration ne, KPlato::Duration oe = Duration::zeroDuration ) { first = ne; second = oe; } }; UsedEffort(); UsedEffort( const UsedEffort &e ); ~UsedEffort(); bool operator==( const UsedEffort &e ) const; bool operator!=( const UsedEffort &e ) const { return !operator==( e ); } void mergeEffort( const UsedEffort &value ); void setEffort( QDate date, const ActualEffort &value ); /// Returns the total effort up to @p date Duration effortTo( QDate date ) const; /// Returns the total effort on @p date ActualEffort effort( QDate date ) const { return m_actual.value( date ); } ActualEffort takeEffort( QDate date ) { return m_actual.take( date ); } /// Returns the total effort for all registered dates Duration effort() const; QDate firstDate() const { return m_actual.firstKey(); } QDate lastDate() const { return m_actual.lastKey(); } QMap actualEffortMap() const { return m_actual; } /// Load from document bool loadXML(KoXmlElement &element, XMLLoaderObject &status ); /// Save to document void saveXML(QDomElement &element) const; bool contains( QDate date ) const { return m_actual.contains( date ); } private: QMap m_actual; }; typedef QMap DateUsedEffortMap; class PLANKERNEL_EXPORT Entry { public: Entry() : percentFinished( 0 ), remainingEffort( Duration::zeroDuration ), totalPerformed( Duration::zeroDuration ) {} Entry( int percent, Duration remaining, Duration performed ) : percentFinished( percent ), remainingEffort( remaining ), totalPerformed( performed ) {} Entry( const Entry &e ) { copy( e ); } bool operator==( const Entry &e ) const { return percentFinished == e.percentFinished && remainingEffort == e.remainingEffort && totalPerformed == e.totalPerformed && note == e.note; } bool operator!=( const Entry &e ) const { return ! operator==( e ); } Entry &operator=(const Entry &e ) { copy( e ); return *this; } int percentFinished; Duration remainingEffort; Duration totalPerformed; QString note; protected: void copy( const Entry &e ) { percentFinished = e.percentFinished; remainingEffort = e.remainingEffort; totalPerformed = e.totalPerformed; note = e.note; } }; typedef QMap EntryList; typedef QHash ResourceUsedEffortMap; explicit Completion( Node *node = 0 ); // review * or &, or at all? Completion( const Completion © ); virtual ~Completion(); bool operator==(const Completion &p); bool operator!=(Completion &p) { return !(*this == p); } Completion &operator=(const Completion &p); /// Load from document bool loadXML(KoXmlElement &element, XMLLoaderObject &status ); /// Save to document void saveXML(QDomElement &element) const; bool startIsValid() const { return m_started && m_startTime.isValid(); } bool isStarted() const { return m_started; } void setStarted( bool on ); bool finishIsValid() const { return m_finished && m_finishTime.isValid(); } bool isFinished() const { return m_finished; } void setFinished( bool on ); DateTime startTime() const { return m_startTime; } void setStartTime( const DateTime &dt ); DateTime finishTime() const { return m_finishTime; } void setFinishTime( const DateTime &dt ); void setPercentFinished( QDate date, int value ); void setRemainingEffort( QDate date, Duration value ); void setActualEffort( QDate date, Duration value ); /// Return a list of the resource that has done any work on this task QList resources() { return m_usedEffort.keys(); } const EntryList &entries() const { return m_entries; } void addEntry( QDate date, Entry *entry ); Entry *takeEntry( QDate date ) { return m_entries.take( date ); changed(); } Entry *entry( QDate date ) const { return m_entries[ date ]; } /// Returns the date of the latest entry QDate entryDate() const; /// Returns the percentFinished of the latest entry int percentFinished() const; /// Returns the percentFinished on @p date int percentFinished( QDate date ) const; /// Returns the estimated remaining effort Duration remainingEffort() const; /// Returns the estimated remaining effort on @p date Duration remainingEffort( QDate date ) const; /// Returns the total actual effort Duration actualEffort() const; /// Returns the total actual effort on @p date Duration actualEffort( QDate date ) const; /// Returns the total actual effort upto and including @p date Duration actualEffortTo( QDate date ) const; /// Returns the actual effort for @p resource on @p date Duration actualEffort( const Resource *resource, QDate date ) const; /// TODO QString note() const; /// TODO void setNote( const QString &str ); /// Returns the total actual cost double actualCost() const; /// Returns the actual cost for @p resource double actualCost( const Resource *resource ) const; /// Returns the actual cost on @p date double actualCost( QDate date ) const; /// Returns the total actual cost for @p resource on @p date double actualCost( const Resource *resource, QDate date ) const; /// Returns the total actual effort and cost upto and including @p date EffortCost actualCostTo( long int id, QDate date ) const; /** * Returns a map of all actual effort and cost entered */ virtual EffortCostMap actualEffortCost( long id, EffortCostCalculationType type = ECCT_All ) const; void addUsedEffort( const Resource *resource, UsedEffort *value = 0 ); UsedEffort *takeUsedEffort( const Resource *r ) { return m_usedEffort.take( const_cast( r ) ); changed(); } UsedEffort *usedEffort( const Resource *r ) const { return m_usedEffort.value( const_cast( r ) ); } const ResourceUsedEffortMap &usedEffortMap() const { return m_usedEffort; } void changed( int property = -1 ); Node *node() const { return m_node; } void setNode( Node *node ) { m_node = node; } enum Entrymode { FollowPlan, EnterCompleted, EnterEffortPerTask, EnterEffortPerResource }; void setEntrymode( Entrymode mode ) { m_entrymode = mode; } Entrymode entrymode() const { return m_entrymode; } void setEntrymode( const QString &mode ); QString entryModeToString() const; QStringList entrymodeList() const; EffortCostMap effortCostPrDay(QDate start, QDate end, long id = -1 ) const; /// Returns the actual effort and cost pr day used by @p resource EffortCostMap effortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE ) const; protected: void copy( const Completion ©); double averageCostPrHour( QDate date, long id ) const; std::pair actualStartEndDates() const; private: Node *m_node; bool m_started, m_finished; DateTime m_startTime, m_finishTime; EntryList m_entries; ResourceUsedEffortMap m_usedEffort; Entrymode m_entrymode; #ifndef NDEBUG public: void printDebug( const QByteArray &ident ) const; #endif }; /** * The WorkPackage class controls work flow for a task */ class PLANKERNEL_EXPORT WorkPackage { public: /// @enum WPTransmitionStatus describes if this package was sent or received enum WPTransmitionStatus { TS_None, /// Not sent nor received TS_Send, /// Package was sent to resource TS_Receive /// Package was received from resource }; explicit WorkPackage( Task *task = 0 ); explicit WorkPackage( const WorkPackage &wp ); virtual ~WorkPackage(); Task *parentTask() const { return m_task; } void setParentTask( Task *task ) { m_task = task; } /// Returns the transmission status of this package WPTransmitionStatus transmitionStatus() const { return m_transmitionStatus; } void setTransmitionStatus( WPTransmitionStatus sts ) { m_transmitionStatus = sts; } static QString transmitionStatusToString( WPTransmitionStatus sts, bool trans = false ); static WPTransmitionStatus transmitionStatusFromString( const QString &sts ); /// Load from document virtual bool loadXML(KoXmlElement &element, XMLLoaderObject &status ); /// Save the full workpackage virtual void saveXML(QDomElement &element) const; /// Load from document virtual bool loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status ); /// Save the full workpackage virtual void saveLoggedXML(QDomElement &element) const; /// Set schedule manager void setScheduleManager( ScheduleManager *sm ); /// Return schedule manager ScheduleManager *scheduleManager() const { return m_manager; } /// Return the schedule id, or NOTSCHEDULED if no schedule manager is set long id() const { return m_manager ? m_manager->scheduleId() : NOTSCHEDULED; } Completion &completion(); const Completion &completion() const; void addLogEntry( DateTime &dt, const QString &str ); QMap log() const; QStringList log(); /// Return a list of resources fetched from the appointments or requests /// merged with resources added to completion QList fetchResources(); /// Return a list of resources fetched from the appointments or requests /// merged with resources added to completion QList fetchResources( long id ); /// Returns id of the resource that owns this package. If empty, task leader owns it. QString ownerId() const { return m_ownerId; } /// Set the resource that owns this package to @p owner. If empty, task leader owns it. void setOwnerId( const QString &id ) { m_ownerId = id; } /// Returns the name of the resource that owns this package. QString ownerName() const { return m_ownerName; } /// Set the name of the resource that owns this package. void setOwnerName( const QString &name ) { m_ownerName = name; } DateTime transmitionTime() const { return m_transmitionTime; } void setTransmitionTime( const DateTime &dt ) { m_transmitionTime = dt; } /// Clear workpackage data void clear(); private: Task *m_task; ScheduleManager *m_manager; Completion m_completion; QString m_ownerName; QString m_ownerId; WPTransmitionStatus m_transmitionStatus; DateTime m_transmitionTime; QMap m_log; }; class PLANKERNEL_EXPORT WorkPackageSettings { public: WorkPackageSettings(); bool loadXML( const KoXmlElement &element ); void saveXML( QDomElement &element) const; bool operator==( WorkPackageSettings settings ) const; bool operator!=( WorkPackageSettings settings ) const; bool usedEffort; bool progress; bool documents; }; /** * A task in the scheduling software is represented by this class. A task * can be anything from 'build house' to 'drill hole' It will always mean * an activity. */ class PLANKERNEL_EXPORT Task : public Node { Q_OBJECT public: explicit Task(Node *parent = 0); explicit Task(const Task &task, Node *parent = 0); ~Task(); /// Return task type. Can be Type_Task, Type_Summarytask ot Type_Milestone. virtual int type() const; /** * 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. */ Duration *getRandomDuration(); /** * Return the resource request made to group * (There should be only one) */ ResourceGroupRequest *resourceGroupRequest(const ResourceGroup *group) const; void clearResourceRequests(); void addRequest(ResourceGroup *group, int numResources); void addRequest(ResourceGroupRequest *request); void takeRequest(ResourceGroupRequest *request); void makeAppointments(); virtual QStringList requestNameList() const; virtual QList requestedResources() const; virtual bool containsRequest( const QString &/*identity*/ ) const; virtual ResourceRequest *resourceRequest( const QString &/*name*/ ) const; /// Return the list of resources assigned to this task virtual QStringList assignedNameList( long id = CURRENTSCHEDULE ) const; /** * Calculates if the assigned resource is overbooked * within the duration of this task */ void calcResourceOverbooked(); /// Load from document virtual bool load(KoXmlElement &element, XMLLoaderObject &status ); /// Save to document virtual void save(QDomElement &element, const XmlSaveContext &context) const; /// Save appointments for schedule with id virtual void saveAppointments(QDomElement &element, long id) const; /// Save a workpackage document with schedule identity @p id void saveWorkPackageXML( QDomElement &element, long id ) const; /** * Returns a list of planned effort and cost for this task * for the interval start, end inclusive */ virtual EffortCostMap plannedEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /** * Returns a list of planned effort and cost for the @p resource * for the interval @p start, @p end inclusive, useng schedule with identity @p id */ virtual EffortCostMap plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// 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; /// Returns the total planned effort for this task (or subtasks) on date virtual Duration plannedEffort(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// 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 date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// 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; /// Returns the total actual effort for this task (or subtasks) on date virtual Duration actualEffort(QDate date ) const; /// Returns the actual effort up to and including date virtual Duration actualEffortTo(QDate date ) const; /** * Returns the total planned cost for this task (or subtasks) */ virtual EffortCost plannedCost( long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Planned cost up to and including date virtual double plannedCostTo(QDate /*date*/, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// Returns actual effort and cost up to and including @p date virtual EffortCost actualCostTo( long int id, QDate date ) const; /** * Returns a list of actual effort and cost for this task * for the interval start, end inclusive */ virtual EffortCostMap actualEffortCostPrDay( QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const; /// 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; /// Returns the effort planned to be used to reach the actual percent finished virtual Duration budgetedWorkPerformed( QDate date, long id = CURRENTSCHEDULE ) const; /// Returns the cost planned to be used to reach the actual percent finished virtual double budgetedCostPerformed( QDate date, long id = CURRENTSCHEDULE ) const; using Node::bcwsPrDay; /// Return map of Budgeted Cost of Work Scheduled pr day virtual EffortCostMap bcwsPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Budgeted Cost of Work Scheduled virtual double bcws( QDate date, long id = CURRENTSCHEDULE ) const; using Node::bcwpPrDay; /// Return map of Budgeted Cost of Work Performed pr day (also includes bcwsPrDay) virtual EffortCostMap bcwpPrDay( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Budgeted Cost of Work Performed virtual double bcwp( long id = CURRENTSCHEDULE ) const; /// Budgeted Cost of Work Performed ( up to @p date ) virtual double bcwp( QDate date, long id = CURRENTSCHEDULE ) const; using Node::acwp; /// Map of Actual Cost of Work Performed virtual EffortCostMap acwp( long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All ); /// Actual Cost of Work Performed up to dat virtual EffortCost acwp( QDate date, long id = CURRENTSCHEDULE ) const; /// Effort based performance index virtual double effortPerformanceIndex( QDate date, long id = CURRENTSCHEDULE ) const; /// Schedule performance index virtual double schedulePerformanceIndex( QDate date, long id = CURRENTSCHEDULE ) const; /// Cost performance index virtual double costPerformanceIndex( long int id, QDate date, bool *error=0 ) const; /** * Return the duration that an activity's start can be delayed * without affecting the project completion date. * An activity with positive float is not on the critical path. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration positiveFloat( long id = CURRENTSCHEDULE ) const; void setPositiveFloat( Duration fl, long id = CURRENTSCHEDULE ) const; /** * Return the duration by which the duration of an activity or path * has to be reduced in order to fulfil a timing- or dependency constraint. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration negativeFloat( long id = CURRENTSCHEDULE ) const; void setNegativeFloat( Duration fl, long id = CURRENTSCHEDULE ) const; /** * Return the duration by which an activity can be delayed or extended * without affecting the start of any succeeding activity. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration freeFloat( long id = CURRENTSCHEDULE ) const; void setFreeFloat( Duration fl, long id = CURRENTSCHEDULE ) const; /** * Return the duration from Early Start to Late Start. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration startFloat( long id = CURRENTSCHEDULE ) const; /** * Return the duration from Early Finish to Late Finish. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration finishFloat( long id = CURRENTSCHEDULE ) const; /** * A task is critical if positive float equals 0 * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ virtual bool isCritical( long id = CURRENTSCHEDULE ) const; /** * Set current schedule to schedule with identity id, for me and my children. * @param id Schedule identity */ virtual void setCurrentSchedule(long id); /** * The assigned resources can not fulfil the estimated effort. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ virtual bool effortMetError( long id = CURRENTSCHEDULE ) const; - + + /// @return true if this task has been started + bool isStarted() const; + Completion &completion() { return m_workPackage.completion(); } const Completion &completion() const { return m_workPackage.completion(); } WorkPackage &workPackage() { return m_workPackage; } const WorkPackage &workPackage() const { return m_workPackage; } int workPackageLogCount() const { return m_packageLog.count(); } QList workPackageLog() const { return m_packageLog; } void addWorkPackage( WorkPackage *wp ); void removeWorkPackage( WorkPackage *wp ); WorkPackage *workPackageAt( int index ) const; QString wpOwnerName() const; WorkPackage::WPTransmitionStatus wpTransmitionStatus() const; DateTime wpTransmitionTime() const; /** * Returns the state of the task * @param id The identity of the schedule used when calculating the state */ virtual uint state( long id = CURRENTSCHEDULE ) const; /// 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; QList parentProxyRelations() const { return m_parentProxyRelations; } QList childProxyRelations() const { return m_childProxyRelations; } /** * Calculates and returns the duration of the node. * Uses the correct expected-, optimistic- or pessimistic effort * dependent on @p use. * @param time Where to start calculation. * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @param backward If true, time specifies when the task should end. */ virtual Duration duration(const DateTime &time, int use, bool backward); /** * Return the duration calculated on bases of the estimates calendar */ Duration length(const DateTime &time, Duration duration, bool backward); Duration length(const DateTime &time, Duration uration, Schedule *sch, bool backward); /// Copy info from parent schedule void copySchedule(); /// Copy intervals from parent schedule void copyAppointments(); /// Copy intervals from parent schedule in the range @p start, @p end void copyAppointments( const DateTime &start, const DateTime &end = DateTime() ); Q_SIGNALS: void workPackageToBeAdded(KPlato::Node *node, int row); void workPackageAdded(KPlato::Node *node); void workPackageToBeRemoved(KPlato::Node *node, int row); void workPackageRemoved(KPlato::Node *node); public: virtual void initiateCalculation(MainSchedule &sch); /** * Sets up the lists used for calculation. * This includes adding summarytasks relations to subtasks * and lists for start- and endnodes. */ virtual void initiateCalculationLists(MainSchedule &sch); /** * Calculates early start and early finish, first for all predeccessors, * then for this task. * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ virtual DateTime calculateForward(int use); /** * Calculates ref m_durationForward from ref earliestStart and * returns the resulting end time (early finish), * which will be used as the successors ref earliestStart. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ virtual DateTime calculateEarlyFinish(int use); /** * Calculates late start and late finish, first for all successors, * then for this task. * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ virtual DateTime calculateBackward(int use); /** * Calculates ref m_durationBackward from ref latestFinish and * returns the resulting start time (late start), * which will be used as the predecessors ref latestFinish. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ virtual DateTime calculateLateStart(int use); /** * Schedules the task within the limits of earliestStart and latestFinish. * Calculates ref m_startTime, ref m_endTime and ref m_duration, * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param earliest The task is not scheduled to start earlier than this * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks endtime which can be used for scheduling the successor. */ virtual DateTime scheduleForward(const DateTime &earliest, int use); /** * Schedules the task within the limits of start time and latestFinish, * Calculates end time and duration. * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks endtime which can be used for scheduling the successor. */ virtual DateTime scheduleFromStartTime(int use); /** * Schedules the task within the limits of earliestStart and latestFinish. * Calculates ref m_startTime, ref m_endTime and ref m_duration, * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param latest The task is not scheduled to end later than this * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks starttime which can be used for scheduling the predeccessor. */ virtual DateTime scheduleBackward(const DateTime &latest, int use); /** * Schedules the task within the limits of end time and latestFinish. * Calculates endTime and duration. * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks starttime which can be used for scheduling the predeccessor. */ virtual DateTime scheduleFromEndTime(int use); /** * Summarytasks (with milestones) need special treatment because * milestones are always 'glued' to their predecessors. */ virtual void adjustSummarytask(); /// Calculate the critical path virtual bool calcCriticalPath(bool fromEnd); virtual void calcFreeFloat(); // Proxy relations are relations to/from summarytasks. // These relations are distributed to the child tasks before calculation. 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 *); public: DateTime earlyStartDate(); void setEarlyStartDate(DateTime value); DateTime earlyFinishDate(); void setEarlyFinishDate(DateTime value); DateTime lateStartDate(); void setLateStartDate(DateTime value); DateTime lateFinishDate(); void setLateFinishDate(DateTime value); int activitySlack(); void setActivitySlack(int value); int activityFreeMargin(); void setActivityFreeMargin(int value); protected: /** * Return the duration calculated on bases of the requested resources */ Duration calcDuration(const DateTime &time, Duration effort, bool backward); private: DateTime calculateSuccessors(const QList &list, int use); DateTime calculatePredeccessors(const QList &list, int use); DateTime scheduleSuccessors(const QList &list, int use); DateTime schedulePredeccessors(const QList &list, int use); /// Fixed duration: Returns @p dt /// Duration with calendar: Returns first available after @p dt /// Has working resource(s) allocated: Returns the earliest time a resource can start work after @p dt, and checks appointments if @p sch is not null. DateTime workTimeAfter(const DateTime &dt, Schedule *sch = 0) const; /// Fixed duration: Returns @p dt /// Duration with calendar: Returns first available before @p dt /// Has working resource(s) allocated: Returns the latest time a resource can finish work, and checks appointments if @p sch is not null. DateTime workTimeBefore(const DateTime &dt, Schedule *sch = 0) const; private: QList m_resource; QList m_parentProxyRelations; QList m_childProxyRelations; // This list store pointers to linked task QList m_requiredTasks; WorkPackage m_workPackage; QList m_packageLog; bool m_calculateForwardRun; bool m_calculateBackwardRun; bool m_scheduleForwardRun; bool m_scheduleBackwardRun; }; } //KPlato namespace Q_DECLARE_METATYPE( KPlato::Completion::UsedEffort::ActualEffort ) #ifndef QT_NO_DEBUG_STREAM PLANKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae ); #endif #endif diff --git a/src/libs/ui/kptscheduleeditor.cpp b/src/libs/ui/kptscheduleeditor.cpp index 501da8cb..ce12cfa3 100644 --- a/src/libs/ui/kptscheduleeditor.cpp +++ b/src/libs/ui/kptscheduleeditor.cpp @@ -1,857 +1,857 @@ /* This file is part of the KDE project Copyright (C) 2006-2011, 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 "kptscheduleeditor.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptduration.h" #include "kptnode.h" #include "kptproject.h" #include "kpttask.h" #include "kptschedule.h" #include "kptdatetime.h" #include "kptpertresult.h" #include "kptitemviewsettup.h" #include "kptrecalculatedialog.h" #include "Help.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KPlato { ScheduleTreeView::ScheduleTreeView( QWidget *parent ) : TreeViewBase( parent ) { header()->setStretchLastSection ( false ); ScheduleItemModel *m = new ScheduleItemModel( this ); setModel( m ); //setSelectionBehavior( QAbstractItemView::SelectItems ); setSelectionMode( QAbstractItemView::SingleSelection ); setSelectionBehavior( QAbstractItemView::SelectRows ); setTreePosition(-1); // always visual index 0 createItemDelegates( m ); } void ScheduleTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel ) { //debugPlan<selectedIndexes() ) { Q_UNUSED(i); //debugPlan<selectedIndexes() ); } void ScheduleTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous ) { //debugPlan<select( current, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); } ScheduleManager *ScheduleTreeView::manager( const QModelIndex &idx ) const { return model()->manager( idx ); } ScheduleManager *ScheduleTreeView::currentManager() const { return model()->manager( currentIndex() ); } QModelIndexList ScheduleTreeView::selectedRows() const { QModelIndexList lst = selectionModel()->selectedRows(); debugPlan<manager( lst.first() ); } return sm; } //----------------------------------- ScheduleEditor::ScheduleEditor(KoPart *part, KoDocument *doc, QWidget *parent) : ViewBase(part, doc, parent) { setupGui(); slotEnableActions(); QVBoxLayout * l = new QVBoxLayout( this ); l->setMargin( 0 ); m_schedulingRange = new SchedulingRange(doc, this); l->addWidget( m_schedulingRange ); m_view = new ScheduleTreeView( this ); connect(this, &ViewBase::expandAll, m_view, &TreeViewBase::slotExpand); connect(this, &ViewBase::collapseAll, m_view, &TreeViewBase::slotCollapse); l->addWidget( m_view ); m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed ); QList show; show << ScheduleModel::ScheduleName << ScheduleModel::ScheduleState << ScheduleModel::ScheduleDirection << ScheduleModel::ScheduleOverbooking << ScheduleModel::ScheduleDistribution << ScheduleModel::SchedulePlannedStart << ScheduleModel::SchedulePlannedFinish << ScheduleModel::ScheduleScheduler << ScheduleModel::ScheduleGranularity ; QList lst; for ( int c = 0; c < model()->columnCount(); ++c ) { if ( ! show.contains( c ) ) { lst << c; } } m_view->setColumnsHidden( lst ); m_view->setDefaultColumns( show ); connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand ); connect( m_view, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) ); connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) ); connect( model(), &QAbstractItemModel::dataChanged, this, &ScheduleEditor::updateActionsEnabled ); connect( m_view, &TreeViewBase::contextMenuRequested, this, &ScheduleEditor::slotContextMenuRequested ); connect( m_view, &TreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested ); Help::add(this, xi18nc("@info:whatsthis", "Schedule Editor" "" "The Schedule Editor is used to create, edit, calculate and delete schedules. " "A schedule can have sub-schedules. A sub-schedule can use the projects progress data" " in order to reschedule only tasks that are not yet finished." " Rescheduling will then use e.g. actual start and remaining effort for the tasks." "More..." "", Help::page("Manual/Schedule_Editor"))); } void ScheduleEditor::draw( Project &project ) { m_view->setProject( &project ); m_schedulingRange->setProject(&project); } void ScheduleEditor::draw() { } void ScheduleEditor::setGuiActive( bool activate ) { //debugPlan<currentIndex().isValid() ) { m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate); } } void ScheduleEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos ) { debugPlan<setContextMenuIndex(index); if ( name.isEmpty() ) { slotHeaderContextMenuRequested( pos ); m_view->setContextMenuIndex(QModelIndex()); return; } debugPlan<setContextMenuIndex(QModelIndex()); } void ScheduleEditor::slotCurrentChanged( const QModelIndex & ) { //debugPlan<selectedRows(); // gets column 0 in each row (should be 1 or 0 rows) if ( lst.count() == 1 ) { ScheduleManager *sm = m_view->model()->manager( lst.first() ); emit scheduleSelectionChanged( sm ); } else { emit scheduleSelectionChanged( 0 ); } slotEnableActions(); } void ScheduleEditor::updateActionsEnabled( const QModelIndex &index ) { debugPlan<setEnabled( false ); actionAddSubSchedule->setEnabled( false ); actionDeleteSelection->setEnabled( false ); actionCalculateSchedule->setEnabled( false ); actionBaselineSchedule->setEnabled( false ); actionMoveLeft->setEnabled( false ); return; } QModelIndexList lst = m_view->selectedRows(); if ( lst.isEmpty() ) { actionAddSchedule->setEnabled( true ); actionAddSubSchedule->setEnabled( false ); actionDeleteSelection->setEnabled( false ); actionCalculateSchedule->setEnabled( false ); actionBaselineSchedule->setEnabled( false ); actionMoveLeft->setEnabled( false ); return; } if ( lst.count() > 1 ) { actionAddSchedule->setEnabled( false ); actionAddSubSchedule->setEnabled( false ); actionDeleteSelection->setEnabled( false ); actionCalculateSchedule->setEnabled( false ); actionBaselineSchedule->setEnabled( false ); actionMoveLeft->setEnabled( false ); return; } // one and only one manager selected ScheduleManager *sm = m_view->manager( lst.first() ); Q_ASSERT( sm ); actionAddSchedule->setEnabled( true ); actionAddSubSchedule->setEnabled( sm->isScheduled() ); actionDeleteSelection->setEnabled( ! ( sm->isBaselined() || sm->isChildBaselined() ) ); actionCalculateSchedule->setEnabled( ! sm->scheduling() && sm->childCount() == 0 && ! ( sm->isBaselined() || sm->isChildBaselined() ) ); const char *const actionBaselineScheduleIconName = sm->isBaselined() ? koIconNameCStr("view-time-schedule-baselined-remove") : koIconNameCStr("view-time-schedule-baselined-add"); actionBaselineSchedule->setIcon(QIcon::fromTheme(QLatin1String(actionBaselineScheduleIconName))); // enable if scheduled and no one else is baselined bool en = sm->isScheduled() && ( sm->isBaselined() || ! m_view->project()->isBaselined() ); actionBaselineSchedule->setEnabled( en ); actionMoveLeft->setEnabled( sm->parentManager() ); } void ScheduleEditor::setupGui() { QString name = "scheduleeditor_edit_list"; actionAddSchedule = new QAction(koIcon("view-time-schedule-insert"), i18n("Add Schedule"), this); actionCollection()->setDefaultShortcut(actionAddSchedule, Qt::CTRL + Qt::Key_I); actionCollection()->addAction("add_schedule", actionAddSchedule ); connect( actionAddSchedule, &QAction::triggered, this, &ScheduleEditor::slotAddSchedule ); addAction( name, actionAddSchedule ); actionAddSubSchedule = new QAction(koIcon("view-time-schedule-child-insert"), i18n("Add Sub-schedule"), this); actionCollection()->setDefaultShortcut(actionAddSubSchedule, Qt::CTRL + Qt::SHIFT + Qt::Key_I); actionCollection()->addAction("add_subschedule", actionAddSubSchedule ); connect( actionAddSubSchedule, &QAction::triggered, this, &ScheduleEditor::slotAddSubSchedule ); addAction( name, actionAddSubSchedule ); actionDeleteSelection = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this ); actionCollection()->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete); actionCollection()->addAction("schedule_delete_selection", actionDeleteSelection ); connect( actionDeleteSelection, &QAction::triggered, this, &ScheduleEditor::slotDeleteSelection ); addAction( name, actionDeleteSelection ); actionCalculateSchedule = new QAction(koIcon("view-time-schedule-calculus"), i18n("Calculate"), this); // actionCollection()->setDefaultShortcut(actionCalculateSchedule, Qt::CTRL + Qt::Key_C); actionCollection()->addAction("calculate_schedule", actionCalculateSchedule ); connect( actionCalculateSchedule, &QAction::triggered, this, &ScheduleEditor::slotCalculateSchedule ); addAction( name, actionCalculateSchedule ); actionBaselineSchedule = new QAction(koIcon("view-time-schedule-baselined-add"), i18n("Baseline"), this); // actionCollection()->setDefaultShortcut(actionBaselineSchedule, Qt::CTRL + Qt::Key_B); actionCollection()->addAction("schedule_baseline", actionBaselineSchedule ); connect( actionBaselineSchedule, &QAction::triggered, this, &ScheduleEditor::slotBaselineSchedule ); addAction( name, actionBaselineSchedule ); actionMoveLeft = new QAction(koIcon("go-first"), xi18nc("@action", "Detach"), this); actionCollection()->addAction("schedule_move_left", actionMoveLeft ); connect( actionMoveLeft, &QAction::triggered, this, &ScheduleEditor::slotMoveLeft ); addAction( name, actionMoveLeft ); // Add the context menu actions for the view options createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig); } void ScheduleEditor::updateReadWrite( bool readwrite ) { debugPlan<setReadWrite( readwrite ); slotEnableActions(); } void ScheduleEditor::slotOptions() { debugPlan; ItemViewSettupDialog *dlg = new ItemViewSettupDialog( this, m_view, true, this ); connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int))); dlg->show(); dlg->raise(); dlg->activateWindow(); } void ScheduleEditor::slotCalculateSchedule() { //debugPlan; ScheduleManager *sm = m_view->selectedManager(); if ( sm == 0 ) { return; } - if ( sm->parentManager() ) { + if ( sm->parentManager() || (sm->isScheduled() && project()->isStarted()) ) { RecalculateDialog dlg; if ( dlg.exec() == QDialog::Rejected ) { return; } sm->setRecalculate( true ); sm->setRecalculateFrom( DateTime( dlg.dateTime() ) ); } emit calculateSchedule( m_view->project(), sm ); } void ScheduleEditor::slotAddSchedule() { //debugPlan; int idx = -1; ScheduleManager *sm = m_view->selectedManager(); if ( sm ) { idx = sm->parentManager() ? sm->parentManager()->indexOf( sm ) : m_view->project()->indexOf( sm ); if ( idx >= 0 ) { ++idx; } } if ( sm && sm->parentManager() ) { sm = sm->parentManager(); ScheduleManager *m = m_view->project()->createScheduleManager( sm->name() + QString(".%1").arg( sm->children().count() + 1 ) ); part()->addCommand( new AddScheduleManagerCmd( sm, m, idx, kundo2_i18n( "Create sub-schedule" ) ) ); QModelIndex idx = model()->index( m ); if ( idx.isValid() ) { m_view->setFocus(); m_view->scrollTo( idx ); m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate ); } } else { Project *p = m_view->project(); ScheduleManager *m = p->createScheduleManager(); AddScheduleManagerCmd *cmd = new AddScheduleManagerCmd( *p, m, idx, kundo2_i18n( "Add schedule %1", m->name() ) ); part() ->addCommand( cmd ); QModelIndex idx = model()->index( m ); if ( idx.isValid() ) { m_view->setFocus(); m_view->scrollTo( idx ); m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate ); } } } void ScheduleEditor::slotAddSubSchedule() { //debugPlan; ScheduleManager *sm = m_view->selectedManager(); if ( sm ) { int row = sm->parentManager() ? sm->parentManager()->indexOf( sm ) : m_view->project()->indexOf( sm ); if ( row >= 0 ) { ++row; } ScheduleManager *m = m_view->project()->createScheduleManager( sm->name() + QString(".%1").arg( sm->children().count() + 1 ) ); part()->addCommand( new AddScheduleManagerCmd( sm, m, row, kundo2_i18n( "Create sub-schedule" ) ) ); m_view->expand( model()->index( sm ) ); QModelIndex idx = model()->index( m ); if ( idx.isValid() ) { m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate ); } } else { slotAddSchedule(); } } void ScheduleEditor::slotBaselineSchedule() { //debugPlan; ScheduleManager *sm = m_view->selectedManager(); if ( sm ) { emit baselineSchedule( m_view->project(), sm ); } } void ScheduleEditor::slotDeleteSelection() { //debugPlan; ScheduleManager *sm = m_view->selectedManager(); if ( sm ) { emit deleteScheduleManager( m_view->project(), sm ); } } void ScheduleEditor::slotMoveLeft() { ScheduleManager *sm = m_view->selectedManager(); if ( sm ) { int index = -1; for ( ScheduleManager *m = sm; m != 0; m = m->parentManager() ) { if ( m->parentManager() == 0 ) { index = m->project().indexOf( m ) + 1; } } debugPlan<name()<loadContext( model()->columnMap(), context ); } void ScheduleEditor::saveContext( QDomElement &context ) const { m_view->saveContext( model()->columnMap(), context ); } KoPrintJob *ScheduleEditor::createPrintJob() { return m_view->createPrintJob( this ); } //----------------------------------------- ScheduleLogTreeView::ScheduleLogTreeView( QWidget *parent ) : QTreeView( parent ) { header()->setStretchLastSection ( true ); header()->setContextMenuPolicy( Qt::CustomContextMenu ); m_model = new QSortFilterProxyModel( this ); m_model->setFilterRole( Qt::UserRole+1 ); m_model->setFilterKeyColumn ( 2 ); // severity m_model->setFilterWildcard( "[^0]" ); // Filter out Debug m_model->setSourceModel( new ScheduleLogItemModel( this ) ); setModel( m_model ); setRootIsDecorated( false ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setSelectionBehavior( QAbstractItemView::SelectRows ); setAlternatingRowColors( true ); connect( header(), &QWidget::customContextMenuRequested, this, &ScheduleLogTreeView::headerContextMenuRequested ); actionShowDebug = new KToggleAction( xi18nc( "@action", "Show Debug Information" ), this ); connect( actionShowDebug, &QAction::toggled, this, &ScheduleLogTreeView::slotShowDebug); } void ScheduleLogTreeView::setFilterWildcard( const QString &filter ) { m_model->setFilterWildcard( filter ); } QRegExp ScheduleLogTreeView::filterRegExp() const { return m_model->filterRegExp(); } void ScheduleLogTreeView::slotShowDebug( bool on ) { on ? setFilterWildcard( QString() ) : setFilterWildcard("[^0]" ); } void ScheduleLogTreeView::contextMenuEvent ( QContextMenuEvent *e ) { debugPlan<pos())<<" at"<pos(); emit contextMenuRequested( indexAt( e->pos() ), e->globalPos() ); } void ScheduleLogTreeView::headerContextMenuRequested( const QPoint &pos ) { //debugPlan<logicalIndexAt(pos)<<" at"<addAction( actionShowDebug ); m->exec( mapToGlobal( pos ) ); delete m; } void ScheduleLogTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel ) { //debugPlan<selectedIndexes() ) { Q_UNUSED(i); //debugPlan<selectedIndexes() ); } void ScheduleLogTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous ) { //debugPlan<select( current, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect ); } void ScheduleLogTreeView::slotEditCopy() { QStringList lst; // int row = 0; QString s; QHeaderView *h = header(); foreach( const QModelIndex &i, selectionModel()->selectedRows() ) { QString s; for ( int section = 0; section < h->count(); ++section ) { QModelIndex idx = model()->index( i.row(), h->logicalIndex( section ) ); if ( ! idx.isValid() || isColumnHidden( idx.column() ) ) { continue; } if ( ! s.isEmpty() ) { s += ' '; } s = QString( "%1%2" ).arg( s ).arg( idx.data().toString(), -10 ); } if ( ! s.isEmpty() ) { lst << s; } } if ( ! lst.isEmpty() ) { QApplication::clipboard()->setText( lst.join( "\n" ) ); } } //----------------------------------- ScheduleLogView::ScheduleLogView(KoPart *part, KoDocument *doc, QWidget *parent) : ViewBase(part, doc, parent ) { setupGui(); slotEnableActions( 0 ); QVBoxLayout * l = new QVBoxLayout( this ); m_view = new ScheduleLogTreeView( this ); l->addWidget( m_view ); // m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed ); connect( m_view, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) ); connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) ); connect( baseModel(), &QAbstractItemModel::dataChanged, this, &ScheduleLogView::updateActionsEnabled ); connect( m_view, &ScheduleLogTreeView::contextMenuRequested, this, &ScheduleLogView::slotContextMenuRequested ); } void ScheduleLogView::setProject( Project *project ) { m_view->setProject( project ); } void ScheduleLogView::draw( Project &project ) { setProject( &project ); } void ScheduleLogView::setGuiActive( bool activate ) { //debugPlan<currentIndex().isValid() ) { m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate); }*/ } void ScheduleLogView::slotEdit() { QString id = sender()->property( "p_identity" ).toString(); if ( id.isEmpty() ) { emit editNode( project() ); return; } Node *n = project()->findNode( id ); if ( n ) { emit editNode( n ); return; } Resource *r = project()->findResource( id ); if ( r ) { emit editResource( r ); return; } warnPlan<<"No object"; } void ScheduleLogView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos ) { if ( ! isReadWrite() || ! index.isValid() ) { return; } QMenu *m = new QMenu( this ); QString id = index.data( ScheduleLogItemModel::IdentityRole ).toString(); if ( id.isEmpty() ) { return; } QAction *a = new QAction(koIcon("document-edit"), i18n( "Edit..." ), m); a->setProperty( "p_identity", id ); m->addAction( a ); connect(a, &QAction::triggered, this, &ScheduleLogView::slotEdit); m->addSeparator(); m->exec( pos ); delete m; } void ScheduleLogView::slotScheduleSelectionChanged( ScheduleManager *sm ) { baseModel()->setManager( sm ); } void ScheduleLogView::slotCurrentChanged( const QModelIndex & ) { //debugPlan<setReadWrite( readwrite ); //slotEnableActions( m_view->currentManager() ); } void ScheduleLogView::slotOptions() { debugPlan; } void ScheduleLogView::slotEditCopy() { m_view->slotEditCopy(); } bool ScheduleLogView::loadContext( const KoXmlElement &/*context */) { debugPlan; return true;//m_view->loadContext( model()->columnMap(), context ); } void ScheduleLogView::saveContext( QDomElement &/*context */) const { //m_view->saveContext( model()->columnMap(), context ); } //--------------------------- ScheduleHandlerView::ScheduleHandlerView(KoPart *part, KoDocument *doc, QWidget *parent ) : SplitterView(part, doc, parent) { debugPlan<<"---------------- Create ScheduleHandlerView ------------------"; m_scheduleEditor = new ScheduleEditor(part, doc, this ); m_scheduleEditor->setObjectName( "ScheduleEditor" ); addView( m_scheduleEditor ); QTabWidget *tab = addTabWidget(); PertResult *p = new PertResult(part, doc, tab); p->setObjectName( "PertResult" ); addView( p, tab, i18n( "Result" ) ); connect( m_scheduleEditor, &ScheduleEditor::scheduleSelectionChanged, p, &PertResult::slotScheduleSelectionChanged ); PertCpmView *c = new PertCpmView(part, doc, tab); c->setObjectName( "PertCpmView" ); addView( c, tab, i18n( "Critical Path" ) ); connect( m_scheduleEditor, &ScheduleEditor::scheduleSelectionChanged, c, &PertCpmView::slotScheduleSelectionChanged ); ScheduleLogView *v = new ScheduleLogView(part, doc, tab); v->setObjectName( "ScheduleLogView" ); addView( v, tab, i18n( "Scheduling Log" ) ); connect( m_scheduleEditor, SIGNAL(scheduleSelectionChanged(KPlato::ScheduleManager*)), v, SLOT(slotScheduleSelectionChanged(KPlato::ScheduleManager*)) ); connect(v, &ScheduleLogView::editNode, this, &ScheduleHandlerView::editNode); connect(v, &ScheduleLogView::editResource, this, &ScheduleHandlerView::editResource); } void ScheduleHandlerView::currentTabChanged( int ) { } ViewBase *ScheduleHandlerView::hitView( const QPoint &/*glpos */) { //debugPlan<"<() ) { v->setGuiActive( active ); } m_activeview = active ? this : 0; emit guiActivated( this, active ); } void ScheduleHandlerView::slotGuiActivated( ViewBase *, bool ) { } QStringList ScheduleHandlerView::actionListNames() const { QStringList lst; foreach ( ViewBase *v, findChildren() ) { lst += v->actionListNames(); } return lst; } QList ScheduleHandlerView::actionList( const QString &name ) const { //debugPlan< lst; foreach ( ViewBase *v, findChildren() ) { lst += v->actionList( name ); } return lst; } SchedulingRange::SchedulingRange(KoDocument *doc, QWidget *parent) : QWidget(parent) , m_doc(doc) , m_project(0) { setupUi(this); connect(targetStartTime, &QAbstractSpinBox::editingFinished, this, &SchedulingRange::slotStartChanged); connect(targetEndTime, &QAbstractSpinBox::editingFinished, this, &SchedulingRange::slotEndChanged); } void SchedulingRange::setProject(Project *project) { if (m_project) { disconnect(m_project, &Project::nodeChanged, this, &SchedulingRange::slotProjectChanged); } m_project = project; if (m_project) { connect(m_project, &Project::nodeChanged, this, &SchedulingRange::slotProjectChanged); slotProjectChanged(m_project); } } void SchedulingRange::slotProjectChanged(Node *node) { if (node != m_project) { return; } if (targetStartTime->dateTime() != m_project->constraintStartTime()) { targetStartTime->setDateTime(m_project->constraintStartTime()); } if (targetEndTime->dateTime() != m_project->constraintEndTime()) { targetEndTime->setDateTime(m_project->constraintEndTime()); } } void SchedulingRange::slotStartChanged() { if (!m_project || !m_doc) { return; } if (targetStartTime->dateTime() == m_project->constraintStartTime()) { return; } ProjectModifyStartTimeCmd *cmd = new ProjectModifyStartTimeCmd(*m_project, targetStartTime->dateTime(), kundo2_i18n("Modify project target start time")); m_doc->addCommand(cmd); } void SchedulingRange::slotEndChanged() { if (!m_project || !m_doc) { return; } if (targetEndTime->dateTime() == m_project->constraintEndTime()) { return; } ProjectModifyEndTimeCmd *cmd = new ProjectModifyEndTimeCmd(*m_project, targetEndTime->dateTime(), kundo2_i18n("Modify project target end time")); m_doc->addCommand(cmd); } } // namespace KPlato