diff --git a/plan/src/libs/kernel/kptcalendar.cpp b/plan/src/libs/kernel/kptcalendar.cpp index 0a0055b7492..5eb86ca1fa2 100644 --- a/plan/src/libs/kernel/kptcalendar.cpp +++ b/plan/src/libs/kernel/kptcalendar.cpp @@ -1,1708 +1,1708 @@ /* This file is part of the KDE project Copyright (C) 2003 - 2007, 2012 Dag Andersen Copyright (C) 2016 Dag Andersen Copyright (C) 2017 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. */ #include "kptcalendar.h" #include "kptappointment.h" #include "kptmap.h" #include "kptduration.h" #include "kptdatetime.h" #include "kptproject.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "kptcommand.h" #include "kptdebug.h" #include #include #ifdef HAVE_KHOLIDAYS #include #include #endif #include #include namespace KPlato { #ifdef HAVE_KHOLIDAYS // start qt-copy (with some changes) // Copyright (C) 2013 John Layt struct TzTimeZone { QString country; QByteArray comment; }; // Define as a type as Q_GLOBAL_STATIC doesn't like it typedef QHash TzTimeZoneHash; // Parse zone.tab table, assume lists all installed zones, if not will need to read directories static TzTimeZoneHash loadTzTimeZones() { QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab"); if (!QFile::exists(path)) path = QStringLiteral("/usr/lib/zoneinfo/zone.tab"); QFile tzif(path); if (!tzif.open(QIODevice::ReadOnly)) return TzTimeZoneHash(); TzTimeZoneHash zonesHash; // TODO QTextStream inefficient, replace later QTextStream ts(&tzif); while (!ts.atEnd()) { const QString line = ts.readLine(); // Comment lines are prefixed with a # if (!line.isEmpty() && line.at(0) != '#') { // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments QStringList parts = line.split(QLatin1Char('\t')); TzTimeZone zone; zone.country = parts.at(0); if (parts.size() > 3) zone.comment = parts.at(3).toUtf8(); zonesHash.insert(parts.at(2).toUtf8(), zone); } } return zonesHash; } // Hash of available system tz files as loaded by loadTzTimeZones() Q_GLOBAL_STATIC_WITH_ARGS(const TzTimeZoneHash, tzZones, (loadTzTimeZones())); // end qt-copy #endif QString CalendarDay::stateToString( int st, bool trans ) { return ( st == None ) ? (trans ? i18n( "Undefined" ) : QStringLiteral( "Undefined" )) : ( st == NonWorking ) ? (trans ? i18n( "Non-working" ) : QStringLiteral( "Non-working" )) : ( st == Working ) ? (trans ? i18n( "Working" ) : QStringLiteral( "Working" )) : QString(); } QStringList CalendarDay::stateList( bool trans ) { QStringList lst; return trans ? lst << i18n( "Undefined" ) << i18n( "Non-working" ) << i18n( "Working" ) : lst << QStringLiteral("Undefined") << QStringLiteral("Non-working") << QStringLiteral("Working"); } ///// CalendarDay //// CalendarDay::CalendarDay() : m_date(), m_state(Undefined), m_calendar( 0 ) { //debugPlan<<"("<second)); me.setAttribute(QStringLiteral("start"), i->first.toString()); } } void CalendarDay::addInterval(TimeInterval *interval) { if (!interval) { return; } // TODO: check for overlapping intervals and handle them for what makes sense QList ::Iterator it; const QList ::Iterator end = m_timeIntervals.end(); QList ::Iterator position = end; for (it = m_timeIntervals.begin(); it != end; ++it) { // first found that is later? if ((*it)->startTime() > interval->startTime()) { // insert before position = it; break; } } m_timeIntervals.insert(position, interval); } bool CalendarDay::operator==(const CalendarDay *day) const { return operator==(*day); } bool CalendarDay::operator==(const CalendarDay &day) const { //debugPlan; if (m_date.isValid() && day.date().isValid()) { if (m_date != day.date()) { //debugPlan<first.toString()<<"-"<second.toString(); return false; } } return true; } bool CalendarDay::operator!=(const CalendarDay *day) const { return operator!=(*day); } bool CalendarDay::operator!=(const CalendarDay &day) const { return !operator==(day); } Duration CalendarDay::effort(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) { // debugPlan<endsMidnight() && start >= i->endTime() ) { //debugPlan<<"Skip:"<="<first.addMSecs(i->second); continue; } QTime t1 = start.addMSecs( length ); if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) { //debugPlan<<"Skip:"<first; continue; } t1 = qMax( start, i->first ); if ( i->endsMidnight() ) { l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1; } else { l = t1.msecsTo( i->endTime() ); } l = qMin( l, length - start.msecsTo( t1 ) ); if ( l <= 0 ) { continue; } //debugPlan<<"Interval:"<"<available( dti ); //FIXME needs an effort method //debugPlan<<"Checked sch:"<first<<" -"<second; d += Duration( (qint64)i->second ); } return d; } TimeInterval CalendarDay::interval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan; return interval( m_date, start, length, timeZone, sch ); } TimeInterval CalendarDay::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan<<"Inp:"< 0 ); Q_ASSERT( QTime(0,0,0).msecsTo( start ) + length <= 1000*60*60*24 ); QTime t1; int l = 0; if ( ! hasInterval() ) { return TimeInterval(); } foreach (TimeInterval *i, m_timeIntervals) { //debugPlan<<"Interval:"<first<second<first.addMSecs(i->second); if ( ! i->endsMidnight() && start >= i->endTime() ) { //debugPlan<<"Skip:"<="<first.addMSecs(i->second); continue; } QTime t1 = start.addMSecs( length ); if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) { //debugPlan<<"Skip:"<first; continue; } t1 = qMax( start, i->first ); if ( i->endsMidnight() ) { l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1; } else { l = t1.msecsTo( i->endTime() ); } l = qMin( l, length - start.msecsTo( t1 ) ); if ( l <= 0 ) { continue; } TimeInterval ti( t1, l ); //debugPlan<<"Day give:"<"<"<second ); } return dur; } void CalendarDay::removeInterval( TimeInterval *ti ) { m_timeIntervals.removeOne(ti); } int CalendarDay::numIntervals() const { return m_state == Working ? m_timeIntervals.count() : 0; } bool CalendarDay::hasInterval(const TimeInterval* interval) const { return m_timeIntervals.contains(const_cast(interval)); } DateTime CalendarDay::start() const { if ( m_state != Working || m_timeIntervals.isEmpty() ) { return DateTime(); } QDate date = m_date; if ( ! m_date.isValid() ) { date = QDate::currentDate(); } if ( m_calendar && m_calendar->timeZone().isValid() ) { return DateTime( date, m_timeIntervals.first()->startTime(), m_calendar->timeZone() ); } return DateTime( date, m_timeIntervals.first()->startTime() ); } DateTime CalendarDay::end() const { if ( m_state != Working || m_timeIntervals.isEmpty() ) { return DateTime(); } QDate date; if ( m_date.isValid() ) { date = m_timeIntervals.last()->endsMidnight() ? m_date.addDays( 1 ) : m_date; } else { date = QDate::currentDate(); } if ( m_calendar && m_calendar->timeZone().isValid() ) { return DateTime( date, m_timeIntervals.last()->endTime(), m_calendar->timeZone() ); } return DateTime( date, m_timeIntervals.last()->endTime() ); } ///// CalendarWeekdays //// CalendarWeekdays::CalendarWeekdays() : m_weekdays() { //debugPlan<<"--->"; for (int i=1; i <= 7; ++i) { m_weekdays.insert( i, new CalendarDay() ); } //debugPlan<<"<---"; } CalendarWeekdays::CalendarWeekdays( const CalendarWeekdays *weekdays ) : m_weekdays() { //debugPlan<<"--->"; copy(*weekdays); //debugPlan<<"<---"; } CalendarWeekdays::~CalendarWeekdays() { qDeleteAll( m_weekdays ); //debugPlan; } const CalendarWeekdays &CalendarWeekdays::copy(const CalendarWeekdays &weekdays) { //debugPlan; qDeleteAll( m_weekdays ); m_weekdays.clear(); QMapIterator i( weekdays.weekdayMap() ); while ( i.hasNext() ) { i.next(); m_weekdays.insert( i.key(), new CalendarDay( i.value() ) ); } return *this; } bool CalendarWeekdays::load( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; bool ok; int dayNo = QString(element.attribute(QStringLiteral("day"),QStringLiteral("-1"))).toInt(&ok); if (dayNo < 0 || dayNo > 6) { errorPlan<<"Illegal weekday: "<load( element, status ) ) day->setState(CalendarDay::None); return true; } void CalendarWeekdays::save(QDomElement &element) const { //debugPlan; QMapIterator i( m_weekdays ); while ( i.hasNext() ) { i.next(); QDomElement me = element.ownerDocument().createElement(QStringLiteral("weekday")); element.appendChild(me); me.setAttribute( QStringLiteral("day"), QString::number(i.key() - 1) ); // 0 (monday) .. 6 (sunday) i.value()->save(me); } } const QMap &CalendarWeekdays::weekdayMap() const { return m_weekdays; } IntMap CalendarWeekdays::stateMap() const { IntMap days; QMapIterator i( m_weekdays ); while ( i.hasNext() ) { i.next(); if ( i.value()->state() != CalendarDay::None ) days.insert( i.key(), i.value()->state() ); } return days; } int CalendarWeekdays::state(QDate date) const { return state( date.dayOfWeek() ); } int CalendarWeekdays::state( int weekday ) const { CalendarDay *day = m_weekdays.value( weekday ); return day ? day->state() : CalendarDay::None; } void CalendarWeekdays::setState(int weekday, int state) { CalendarDay *day = m_weekdays.value( weekday ); if ( day == 0 ) return; day->setState(state); } QList CalendarWeekdays::intervals(int weekday) const { CalendarDay *day = m_weekdays.value( weekday ); Q_ASSERT(day); return day->timeIntervals(); } void CalendarWeekdays::setIntervals(int weekday, const QList &intervals) { CalendarDay *day = m_weekdays.value( weekday ); if (day) { day->setIntervals( intervals ); } } void CalendarWeekdays::clearIntervals(int weekday) { CalendarDay *day = m_weekdays.value( weekday ); if (day) { day->clearIntervals(); } } bool CalendarWeekdays::operator==(const CalendarWeekdays *wd) const { if (m_weekdays.count() != wd->weekdays().count()) { return false; } QMapIterator i( wd->weekdayMap() ); while ( i.hasNext() ) { i.next(); CalendarDay *day1 = i.value(); CalendarDay *day2 = m_weekdays.value( i.key() ); if (day1 != day2) return false; } return true; } bool CalendarWeekdays::operator!=(const CalendarWeekdays *wd) const { return operator==( wd ) == false; } Duration CalendarWeekdays::effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) { // debugPlan<<"Day of week="<state() == CalendarDay::Working) { return day->effort(date, start, length, timeZone, sch); } return Duration::zeroDuration; } TimeInterval CalendarWeekdays::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan; CalendarDay *day = weekday( date.dayOfWeek() ); if (day && day->state() == CalendarDay::Working) { return day->interval(date, start, length, timeZone, sch); } return TimeInterval(); } bool CalendarWeekdays::hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan<hasInterval(date, start, length, timeZone, sch); } bool CalendarWeekdays::hasInterval() const { //debugPlan; foreach ( CalendarDay *d, m_weekdays ) { if (d->hasInterval()) return true; } return false; } CalendarDay *CalendarWeekdays::weekday( int day ) const { Q_ASSERT( day >= 1 && day <= 7 ); Q_ASSERT( m_weekdays.contains( day ) ); return m_weekdays.value( day ); } //static int CalendarWeekdays::dayOfWeek(const QString& name) { QStringList lst; lst << QStringLiteral("Monday") << QStringLiteral("Tuesday") << QStringLiteral("Wednesday") << QStringLiteral("Thursday") << QStringLiteral("Friday") << QStringLiteral("Saturday") << QStringLiteral("Sunday"); int idx = -1; if ( lst.contains( name ) ) { idx = lst.indexOf( name ) + 1; } return idx; } Duration CalendarWeekdays::duration() const { Duration dur; foreach ( CalendarDay *d, m_weekdays ) { dur += d->duration(); } return dur; } Duration CalendarWeekdays::duration(int _weekday) const { CalendarDay *day = weekday(_weekday); if (day) return day->duration(); return Duration(); } int CalendarWeekdays::indexOf( const CalendarDay *day ) const { return m_weekdays.values().indexOf( const_cast(day) ); } ///// Calendar //// Calendar::Calendar() : QObject( 0 ), // don't use parent m_parent(0), m_project(0), m_default( false ), m_shared(false) { init(); } Calendar::Calendar(const QString& name, Calendar *parent) : QObject( 0 ), // don't use parent m_name(name), m_parent(parent), m_project(0), m_days(), m_default( false ), m_shared(false) { init(); } Calendar::~Calendar() { //debugPlan<<"deleting"<regionCode()); #endif foreach (CalendarDay *d, calendar.days()) { m_days.append(new CalendarDay(d)); } delete m_weekdays; m_weekdays = new CalendarWeekdays(calendar.weekdays()); return *this; } void Calendar::init() { #ifdef HAVE_KHOLIDAYS m_region = new KHolidays::HolidayRegion(); #endif m_weekdays = new CalendarWeekdays(); m_timeZone = QTimeZone::systemTimeZone(); m_cacheversion = 0; m_blockversion = false; } int Calendar::cacheVersion() const { return m_parent ? m_parent->cacheVersion() : m_cacheversion; } void Calendar::incCacheVersion() { if ( m_blockversion ) { return; } if ( m_parent ) { m_parent->incCacheVersion(); } else { ++m_cacheversion; debugPlan<setCacheVersion( version ); } else { m_cacheversion = version; debugPlan<changed( this ); } } void Calendar::setParentCal( Calendar *parent, int pos ) { if ( m_parent ) { m_parent->takeCalendar( this ); } m_parent = parent; if ( m_parent ) { m_parent->addCalendar( this, pos ); } } bool Calendar::isChildOf( const Calendar *cal ) const { Calendar *p = parentCal(); for (; p != 0; p = p->parentCal() ) { if ( cal == p ) { return true; } } return false; } void Calendar::setProject(Project *project) { m_project = project; } void Calendar::setTimeZone( const QTimeZone &tz ) { if (m_timeZone == tz) { return; } //debugPlan<name(); m_timeZone = tz; #ifdef HAVE_KHOLIDAYS if (m_regionCode == QLatin1String("Default")) { setHolidayRegion(QStringLiteral("Default")); } #endif if ( m_project ) { m_project->changed( this ); } incCacheVersion(); } QTimeZone Calendar::projectTimeZone() const { return m_project ? m_project->timeZone() : QTimeZone::systemTimeZone(); } void Calendar::setDefault( bool on ) { m_default = on; if ( m_project ) { m_project->changed( this ); } incCacheVersion(); } // Note: only project should do this void Calendar::setId(const QString& id) { //debugPlan<setTimeZone( m_timeZone ); } void Calendar::takeCalendar( Calendar *calendar ) { int i = indexOf( calendar ); if ( i != -1 ) { m_calendars.removeAt( i ); } } int Calendar::indexOf( const Calendar *calendar ) const { return m_calendars.indexOf( const_cast(calendar) ); } bool Calendar::loadCacheVersion( KoXmlElement &element, XMLLoaderObject &status ) { Q_UNUSED(status); m_cacheversion = element.attribute( QStringLiteral("version"), 0 ).toInt(); debugPlan<load( e, status ) ) return false; } if (e.tagName() == QLatin1String("day")) { CalendarDay *day = new CalendarDay(); if ( day->load( e, status ) ) { if (!day->date().isValid()) { delete day; errorPlan<date()); if (d) { // already exists, keep the new delete takeDay(d); warnPlan<id()); } me.setAttribute(QStringLiteral("name"), m_name); me.setAttribute(QStringLiteral("id"), m_id); if ( m_default ) { me.setAttribute(QStringLiteral("default"), QString::number(m_default)); } me.setAttribute(QStringLiteral("timezone"), m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString()); m_weekdays->save(me); foreach (CalendarDay *d, m_days) { QDomElement e = me.ownerDocument().createElement(QStringLiteral("day")); me.appendChild(e); d->save(e); } me.setAttribute(QStringLiteral("shared"), m_shared); #ifdef HAVE_KHOLIDAYS me.setAttribute(QStringLiteral("holiday-region"), m_regionCode); #endif saveCacheVersion( me ); } int Calendar::state(QDate date) const { CalendarDay *day = findDay( date ); if ( day && day->state() != CalendarDay::Undefined ) { return day->state(); } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return CalendarDay::NonWorking; } #endif day = weekday( date.dayOfWeek() ); if ( day && day->state() != CalendarDay::Undefined ) { return day->state(); } return m_parent ? m_parent->state( date ) : CalendarDay::Undefined; } CalendarDay *Calendar::findDay(QDate date, bool skipUndefined) const { //debugPlan<date() == date) { if (skipUndefined && d->state() == CalendarDay::Undefined) { continue; // hmmm, break? } return d; } } //debugPlan<setState( state ); emit changed( day ); incCacheVersion(); } void Calendar::addWorkInterval( CalendarDay *day, TimeInterval *ti ) { emit workIntervalToBeAdded( day, ti, day->numIntervals() ); day->addInterval( ti ); emit workIntervalAdded( day, ti ); incCacheVersion(); } void Calendar::takeWorkInterval( CalendarDay *day, TimeInterval *ti ) { if ( !day->hasInterval(ti) ) { return; } emit workIntervalToBeRemoved( day, ti ); day->removeInterval( ti ); emit workIntervalRemoved( day, ti ); incCacheVersion(); return; } void Calendar::setWorkInterval( TimeInterval *ti, const TimeInterval &value ) { *ti = value; emit changed( ti ); incCacheVersion(); } void Calendar::setDate( CalendarDay *day, QDate date ) { day->setDate( date ); emit changed( day ); incCacheVersion(); } CalendarDay *Calendar::day( QDate date ) const { foreach ( CalendarDay *d, m_days ) { if ( d->date() == date ) { return d; } } return 0; } IntMap Calendar::weekdayStateMap() const { return m_weekdays->stateMap(); } void Calendar::setWeekday( int dayno, const CalendarDay &day ) { if ( dayno < 1 || dayno > 7 ) { return; } CalendarDay *wd = weekday( dayno ); while ( ! wd->timeIntervals().isEmpty() ) { TimeInterval *ti = wd->timeIntervals().last(); emit workIntervalToBeRemoved( wd, ti ); wd->removeInterval( ti ); emit workIntervalRemoved( wd, ti ); } wd->setState( day.state() ); emit changed( wd ); foreach ( TimeInterval *ti, day.timeIntervals() ) { TimeInterval *t = new TimeInterval( *ti ); emit workIntervalToBeAdded( wd, t, wd->numIntervals() ); // hmmmm wd->addInterval( t ); emit workIntervalAdded( wd, t ); } incCacheVersion(); } bool Calendar::hasParent(Calendar *cal) { //debugPlan; if (!m_parent) return false; if (m_parent == cal) return true; return m_parent->hasParent(cal); } AppointmentIntervalList Calendar::workIntervals( const QDateTime &start, const QDateTime &end, double load ) const { //debugPlan< start.date()) { startTime = QTime(0, 0, 0); } if (date < end.date()) { length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1; } else { length = startTime.msecsTo( end.time() ); } if ( length <= 0 ) { break; } res = firstInterval( date, startTime, length ); while ( res.isValid() ) { //debugPlan<<"interval:"<= end ) { warnPlan<<"Invalid interval"; return lst; } Q_ASSERT(m_timeZone.isValid()); QDateTime zonedStart = start.toTimeZone( m_timeZone ); QDateTime zonedEnd = end.toTimeZone( m_timeZone ); Q_ASSERT( zonedStart.isValid() && zonedEnd.isValid() ); return workIntervals( zonedStart, zonedEnd, load ); } Duration Calendar::effort(QDate date, QTime start, int length, Schedule *sch) const { // debugPlan<"<state() == CalendarDay::Working) { return day->effort(start, length, m_timeZone, sch); } else if (day->state() == CalendarDay::NonWorking) { return Duration::zeroDuration; } else { errorPlan<<"Invalid state: "<state(); return Duration::zeroDuration; } } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return Duration::zeroDuration; } #endif // check my own weekdays if (m_weekdays) { if (m_weekdays->state(date) == CalendarDay::Working) { return m_weekdays->effort(date, start, length, m_timeZone, sch); } if (m_weekdays->state(date) == CalendarDay::NonWorking) { return Duration::zeroDuration; } } if (m_parent) { return m_parent->effort(date, start, length, sch); } return Duration::zeroDuration; } Duration Calendar::effort(const QDateTime &start, const QDateTime &end, Schedule *sch) const { // debugPlan< t0 ) { eff += effort(date, t0, t0.msecsTo( endTime ), sch); // last day } //debugPlan<<": eff now="<resource() ) debugPlan<resource()->name()<name()<<"Available:"<resource()->availableFrom()<resource()->availableUntil(); errorPlan<<"Illegal datetime: "<interval(startTime, length, m_timeZone, sch); } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return TimeInterval(); } #endif if (m_weekdays) { if (m_weekdays->state(date) == CalendarDay::Working) { //debugPlan<<"Check weekday"; TimeInterval i = m_weekdays->interval(date, startTime, length, m_timeZone, sch); //debugPlan<<"Checked weekday, got"<state(date) == CalendarDay::NonWorking) { return TimeInterval(); } } if (m_parent) { //debugPlan<<"Check parent"; return m_parent->firstInterval(date, startTime, length, sch); } return TimeInterval(); } DateTimeInterval Calendar::firstInterval( const QDateTime &start, const QDateTime &end, Schedule *sch) const { TimeInterval res; QTime startTime = start.time(); int length = 0; if ( start.date() == end.date() ) { // Handle single day length = startTime.msecsTo( end.time() ); if ( length <= 0 ) { warnPlan<<"Invalid length"< start.date()) { startTime = QTime(0, 0, 0); } if (date < end.date()) { length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1; } else { length = startTime.msecsTo( end.time() ); } if ( length <= 0 ) { break; } //debugPlan<<"Check:"<= end ) { warnPlan<<"Invalid interval"<limit?"":"(time>limit)"); return DateTime(); } if ( time == limit ) { return DateTime(); } Q_ASSERT( m_timeZone.isValid() ); QDateTime zonedTime = time.toTimeZone( m_timeZone ); QDateTime zonedLimit = limit.toTimeZone( m_timeZone ); Q_ASSERT( zonedTime.isValid() && zonedLimit.isValid() ); return firstInterval( zonedTime, zonedLimit, sch ).first; } DateTime Calendar::firstAvailableBefore(const QDateTime &time, const QDateTime &limit, Schedule *sch) { debugPlan<findCalendar(id) : 0); } bool Calendar::removeId(const QString &id) { return (m_project ? m_project->removeCalendarId(id) : false); } void Calendar::insertId(const QString &id){ if (m_project) m_project->insertCalendarId(id, this); } void Calendar::addDay( CalendarDay *day ) { emit dayToBeAdded( day, 0 ); m_days.insert(0, day); emit dayAdded( day ); incCacheVersion(); } CalendarDay *Calendar::takeDay(CalendarDay *day) { int i = m_days.indexOf(day); if (i == -1) { return 0; } emit dayToBeRemoved( day ); m_days.removeAt(i); emit dayRemoved( day ); incCacheVersion(); return day; } -QList > Calendar::consecutiveVacationDays() const +QList > Calendar::consecutiveVacationDays() const { - QList > lst; - QPair interval( 0, 0 ); + QList > lst; + std::pair interval( 0, 0 ); foreach ( CalendarDay* day, m_days ) { if ( day->state() == CalendarDay::NonWorking ) { if ( interval.first == 0 ) { interval.first = day; } interval.second = day; } else { if ( interval.first != 0 ) { - lst << QPair( interval ); + lst << std::pair( interval ); } interval.first = interval.second = 0; } } return lst; } QList Calendar::workingDays() const { QList lst; foreach ( CalendarDay* day, m_days ) { if ( day->state() == CalendarDay::Working ) { lst << day; } } return lst; } bool Calendar::isShared() const { return m_shared; } void Calendar::setShared(bool on) { m_shared = on; } #ifdef HAVE_KHOLIDAYS bool Calendar::isHoliday(QDate date) const { if (m_region->isValid()) { KHolidays::Holiday::List lst = m_region->holidays(date); if (!lst.isEmpty() && lst.first().dayType() != KHolidays::Holiday::Workday) { return true; } } return false; } KHolidays::HolidayRegion *Calendar::holidayRegion() const { return m_region; } void Calendar::setHolidayRegion(const QString &code) { delete m_region; m_regionCode = code; if (code == QLatin1String("Default")) { QString country; if (m_timeZone.isValid()) { // TODO be more accurate when country has multiple timezones/regions country = tzZones->value(m_timeZone.id()).country; } m_region = new KHolidays::HolidayRegion(KHolidays::HolidayRegion::defaultRegionCode(country)); } else { m_region = new KHolidays::HolidayRegion(code); } debugPlan<"<isValid(); emit changed(static_cast(0)); if (m_project) { m_project->changed(this); } } QString Calendar::holidayRegionCode() const { return m_regionCode; } QStringList Calendar::holidayRegionCodes() const { QStringList lst = KHolidays::HolidayRegion::regionCodes(); lst.removeDuplicates(); return lst; } #endif ///////////// StandardWorktime::StandardWorktime( Project *project ) : m_project( project ) { init(); } StandardWorktime::StandardWorktime(StandardWorktime *worktime) { if (worktime) { m_year = worktime->durationYear(); m_month = worktime->durationMonth(); m_week = worktime->durationWeek(); m_day = worktime->durationDay(); } else { init(); } } StandardWorktime::~StandardWorktime() { //debugPlan<<"("<changed( this ); } } QList StandardWorktime::scales() const { return QList() << m_year.milliseconds() << m_month.milliseconds() << m_week.milliseconds() << m_day.milliseconds() << 60*60*1000 << 60*1000 << 1000 << 1; } bool StandardWorktime::load( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; m_year = Duration::fromString(element.attribute(QStringLiteral("year")), Duration::Format_Hour); m_month = Duration::fromString(element.attribute(QStringLiteral("month")), Duration::Format_Hour); m_week = Duration::fromString(element.attribute(QStringLiteral("week")), Duration::Format_Hour); m_day = Duration::fromString(element.attribute(QStringLiteral("day")), Duration::Format_Hour); KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == QLatin1String("calendar")) { // pre 0.6 version stored base calendar in standard worktime if ( status.version() >= QLatin1String("0.6") ) { warnPlan<<"Old format, calendar in standard worktime"; warnPlan<<"Tries to load anyway"; } // try to load anyway Calendar *calendar = new Calendar; if ( calendar->load( e, status ) ) { status.project().addCalendar( calendar ); calendar->setDefault( true ); status.project().setDefaultCalendar( calendar ); // hmmm status.setBaseCalendar( calendar ); } else { delete calendar; errorPlan<<"Failed to load calendar"; } } } return true; } void StandardWorktime::save(QDomElement &element) const { //debugPlan; QDomElement me = element.ownerDocument().createElement(QStringLiteral("standard-worktime")); element.appendChild(me); me.setAttribute(QStringLiteral("year"), m_year.toString(Duration::Format_Hour)); me.setAttribute(QStringLiteral("month"), m_month.toString(Duration::Format_Hour)); me.setAttribute(QStringLiteral("week"), m_week.toString(Duration::Format_Hour)); me.setAttribute(QStringLiteral("day"), m_day.toString(Duration::Format_Hour)); } } //KPlato namespace diff --git a/plan/src/libs/kernel/kptcalendar.h b/plan/src/libs/kernel/kptcalendar.h index c99a5df1f8e..93a26d07e3b 100644 --- a/plan/src/libs/kernel/kptcalendar.h +++ b/plan/src/libs/kernel/kptcalendar.h @@ -1,695 +1,695 @@ /* This file is part of the KDE project Copyright (C) 2003 - 2007 Dag Andersen Copyright (C) 2011 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KPTCALENDAR_H #define KPTCALENDAR_H #include "kptdatetime.h" #include "kptduration.h" #include "kptdebug.h" #include "kplatokernel_export.h" -#include +#include #include #include #include #include #ifdef HAVE_KHOLIDAYS namespace KHolidays { class HolidayRegion; } #endif class KUndo2Command; class QDomElement; class QStringList; /// The main namespace. namespace KPlato { class Calendar; class Project; class IntMap; -class DateTime; +//class DateTime; class Project; class Schedule; class XMLLoaderObject; class AppointmentIntervalList; -class KPLATOKERNEL_EXPORT DateTimeInterval : public QPair +class KPLATOKERNEL_EXPORT DateTimeInterval : public std::pair { public: DateTimeInterval() - : QPair() + : std::pair() {} DateTimeInterval( const DateTime &t1, const DateTime &t2 ) - : QPair( t1, t2 ) + : std::pair( t1, t2 ) {} DateTimeInterval &operator=( const DateTimeInterval &other ) { first = other.first; second = other.second; return *this; } bool isValid() const { return first.isValid() && second.isValid(); } void limitTo( const DateTime &start, const DateTime &end ) { if ( ! first.isValid() || ( start.isValid() && start > first ) ) { first = start; } if ( ! second.isValid() || ( end.isValid() && end < second ) ) { second = end; } if ( isValid() && first > second ) { first = second = DateTime(); } } void limitTo( const DateTimeInterval &interval ) { limitTo( interval.first, interval.second ); } DateTimeInterval limitedTo( const DateTime &start, const DateTime &end ) const { DateTimeInterval i = *this; i.limitTo( start, end ); return i; } DateTimeInterval limitedTo( const DateTimeInterval &interval ) const { return limitedTo( interval.first, interval.second ); } QString toString() const { return QStringLiteral( "%1 to %2" ) .arg( first.isValid()?first.toString():QStringLiteral("''") ) .arg( second.isValid()?second.toString():QStringLiteral("''") ); } }; /// TimeInterval is defined as a start time and a length. /// The end time (start + length) must not exceed midnight -class KPLATOKERNEL_EXPORT TimeInterval : public QPair +class KPLATOKERNEL_EXPORT TimeInterval : public std::pair { public: TimeInterval() - : QPair( QTime(), -1 ) + : std::pair( QTime(), -1 ) {} - explicit TimeInterval( QPair value ) - : QPair( value ) + explicit TimeInterval( std::pair value ) + : std::pair( value ) { init(); } TimeInterval( QTime start, int length ) - : QPair( start, length ) + : std::pair( start, length ) { init(); } TimeInterval( const TimeInterval &value ) - : QPair( value.first, value.second ) + : std::pair( value.first, value.second ) { init(); } /// Return the intervals start time QTime startTime() const { return first; } /// Return the intervals calculated end time. Note: It may return QTime(0,0,0) QTime endTime() const { return first.addMSecs( second ); } double hours() const { return (double)(second) / ( 1000. * 60. * 60. ); } /// Returns true if this interval ends at midnight, and thus endTime() returns QTime(0,0,0) bool endsMidnight() const { return endTime() == QTime( 0, 0, 0 ); } bool isValid() const { return first.isValid() && second > 0; } bool isNull() const { return first.isNull() || second < 0; } TimeInterval &operator=( const TimeInterval &ti ) { first = ti.first; second = ti.second; return *this; } /// Returns true if the intervals overlap in any way bool intersects( const TimeInterval &ti ) const { if ( ! isValid() || ! ti.isValid() ) { return false; } if ( endsMidnight() && ti.endsMidnight() ) { return true; } if ( endsMidnight() ) { return first < ti.endTime(); } if ( ti.endsMidnight() ) { return ti.first < endTime(); } return ( first < ti.endTime() && endTime() > ti.first ) || ( ti.first < endTime() && ti.endTime() > first ); } protected: void init() { int s = QTime( 0, 0, 0 ).msecsTo( first ); if ( ( s + second ) > 86400000 ) { second = 86400000 - s; errorPlan<<"Overflow, limiting length to"< timeIntervals() const { return m_timeIntervals; } void addInterval( QTime t1, int length ) { addInterval( new TimeInterval( t1, length ) ); } /** * Caller needs to ensure that intervals are not overlapping. */ void addInterval(TimeInterval *interval); void addInterval(TimeInterval interval) { addInterval(new TimeInterval(interval)); } void clearIntervals() { m_timeIntervals.clear(); } void setIntervals(const QList &intervals) { m_timeIntervals.clear(); m_timeIntervals = intervals; } void removeInterval( TimeInterval *interval ); bool hasInterval( const TimeInterval *interval ) const; int numIntervals() const; DateTime start() const; DateTime end() const; QDate date() const { return m_date; } void setDate(QDate date) { m_date = date; } int state() const { return m_state; } void setState(int state) { m_state = state; } bool operator==(const CalendarDay *day) const; bool operator==(const CalendarDay &day) const; bool operator!=(const CalendarDay *day) const; bool operator!=(const CalendarDay &day) const; Duration workDuration() const; /** * Returns the amount of 'worktime' that can be done on * this day between the times start and end. */ Duration effort(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the amount of 'worktime' that can be done on * this day between the times start and end. */ Duration effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the actual 'work interval' for the interval start to end. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. */ TimeInterval interval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; /** * Returns the actual 'work interval' for the interval start to end. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. */ TimeInterval interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; bool hasInterval() const; /** * Returns true if at least a part of a 'work interval' exists * for the interval start to end. */ bool hasInterval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval @p start to @p start + @p length. * Assumes this day is date. (Used by weekday hasInterval().) * If @p sch is not 0, the schedule is checked for availability. */ bool hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; Duration duration() const; const CalendarDay ©(const CalendarDay &day); static QString stateToString( int st, bool trans = false ); static QStringList stateList( bool trans = false ); private: QDate m_date; //NOTE: inValid if used for weekdays int m_state; Calendar *m_calendar; QList m_timeIntervals; #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; class KPLATOKERNEL_EXPORT CalendarWeekdays { public: CalendarWeekdays(); explicit CalendarWeekdays( const CalendarWeekdays *weekdays ); ~CalendarWeekdays(); bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; const QList weekdays() const { QList lst = m_weekdays.values(); return lst; } /** * Returns the pointer to CalendarDay for day. * @param day The weekday number, must be between 1 (monday) and 7 (sunday) */ CalendarDay *weekday(int day) const; CalendarDay *weekday(QDate date) const { return weekday(date.dayOfWeek()); } static int dayOfWeek( const QString &name ); const QMap &weekdayMap() const; IntMap stateMap() const; // void setWeekday(IntMap::iterator it, int state) { m_weekdays.at(it.key())->setState(state); } int state(QDate date) const; int state(int weekday) const; void setState(int weekday, int state); QList intervals(int weekday) const; void setIntervals(int weekday, const QList &intervals); void clearIntervals(int weekday); bool operator==(const CalendarWeekdays *weekdays) const; bool operator!=(const CalendarWeekdays *weekdays) const; Duration effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the actual 'work interval' on the weekday defined by date * for the interval @p start to @p start + @p length. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. * If @p sch is not 0, the schedule is checked for availability. */ TimeInterval interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const; /** * Returns true if at least a part of a 'work interval' exists * on the weekday defined by date for the interval start to end. */ bool hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const; bool hasInterval() const; Duration duration() const; Duration duration(int weekday) const; const CalendarWeekdays ©(const CalendarWeekdays &weekdays); int indexOf( const CalendarDay *day ) const; private: Calendar *m_calendar; QMap m_weekdays; #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; /** * Calendar defines the working and nonworking days and hours. * A day can have the three states Undefined, NonWorking, or Working. * A calendar can have a parent calendar that defines the days that are * undefined in this calendar. * If a calendar have no parent, an undefined day defaults to Nonworking. * A Working day has one or more work intervals to define the work hours. * * The definition can consist of two parts: Weekdays and Day. * Day has highest priority. * * A typical calendar hierarchy could include calendars on 4 levels: * 1. Definition of normal weekdays and national holidays/vacation days. * 2. Definition of the company's special workdays/-time and vacation days. * 3. Definitions for groups of resources. * 4. Definitions for individual resources. * * A calendar can define a timezone different from the projects. * This enables planning with resources that does not recide in the same place. * */ class KPLATOKERNEL_EXPORT Calendar : public QObject { Q_OBJECT public: Calendar(); explicit Calendar(const QString& name, Calendar *parent=0); //Calendar( const Calendar &c ); QObject doesn't allow a copy constructor ~Calendar(); const Calendar &operator=(const Calendar &calendar ) { return copy( calendar ); } QString name() const { return m_name; } void setName(const QString& name); Calendar *parentCal() const { return m_parent; } /** * Set parent calendar to @p parent. * Removes myself from current parent and * inserts myself as child to new parent. */ void setParentCal( Calendar *parent, int pos = -1 ); bool isChildOf( const Calendar *cal ) const; Project *project() const { return m_project; } void setProject(Project *project); QString id() const { return m_id; } void setId(const QString& id); const QList &calendars() const { return m_calendars; } void addCalendar( Calendar *calendar, int pos = -1 ); void takeCalendar( Calendar *calendar ); int indexOf( const Calendar *calendar ) const; /// Return number of children int childCount() const { return m_calendars.count(); } /// Return child calendar at @p index, 0 if index out of bounds Calendar *childAt( int index ) const { return m_calendars.value( index ); } bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; int state(QDate date) const; void setState( CalendarDay *day, CalendarDay::State state ); void addWorkInterval( CalendarDay *day, TimeInterval *ti ); void takeWorkInterval( CalendarDay *day, TimeInterval *ti ); void setWorkInterval( TimeInterval *ti, const TimeInterval &value ); /** * Find the definition for the day @p date. * If @p skipUndefined = true the day is NOT returned if it has state Undefined. */ CalendarDay *findDay(QDate date, bool skipUndefined=false) const; void addDay(CalendarDay *day); CalendarDay *takeDay(CalendarDay *day); const QList &days() const { return m_days; } - QList > consecutiveVacationDays() const; + QList > consecutiveVacationDays() const; QList workingDays() const; int indexOf( const CalendarDay *day ) const { return m_days.indexOf( const_cast( day ) ); } CalendarDay *dayAt( int index ) { return m_days.value( index ); } int numDays() const { return m_days.count(); } void setDate( CalendarDay *day, QDate date ); CalendarDay *day( QDate date ) const; IntMap weekdayStateMap() const; CalendarWeekdays *weekdays() const { return m_weekdays; } CalendarDay *weekday(int day) const { return m_weekdays->weekday(day); } int indexOfWeekday( const CalendarDay *day ) const { return m_weekdays->indexOf( day ); } const QList weekdayList() const { return m_weekdays->weekdays(); } int numWeekdays() const { return weekdayList().count(); } /// Sets the @p weekday data to the data in @p day void setWeekday( int weekday, const CalendarDay &day ); QString parentId() const { return m_parentId; } void setParentId(const QString& id) { m_parentId = id; } bool hasParent(Calendar *cal); /** * Returns the work intervals in the interval from @p start to @p end * Sets the load of each interval to @p load */ AppointmentIntervalList workIntervals(const DateTime &start, const DateTime &end, double load) const; /** * Returns the amount of 'worktime' that can be done in the * interval from @p start to @p end * If @p sch is not 0, the schedule is checked for availability. */ Duration effort(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Returns the first 'work interval' for the interval * starting at @p start and ending at @p end. * If no 'work interval' exists, returns an interval with invalid DateTime. * You can also use @ref hasInterval() to check if a 'work interval' exists. * If @p sch is not 0, the schedule is checked for availability. */ DateTimeInterval firstInterval(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval starting at @p start and ending at @p end. * If @p sch is not 0, the schedule is checked for availability. */ bool hasInterval(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Find the first available time after @p time before @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableAfter(const DateTime &time, const DateTime &limit, Schedule *sch = 0); /** * Find the first available time backwards from @p time. Search until @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableBefore(const DateTime &time, const DateTime &limit, Schedule *sch = 0); Calendar *findCalendar() const { return findCalendar(m_id); } Calendar *findCalendar(const QString &id) const; bool removeId() { return removeId(m_id); } bool removeId(const QString &id); void insertId(const QString &id); QTimeZone timeZone() const { return m_timeZone; } void setTimeZone( const QTimeZone &tz ); /// Return the project timezone, or local timezone if no project QTimeZone projectTimeZone() const; void setDefault( bool on ); bool isDefault() const { return m_default; } int cacheVersion() const; void incCacheVersion(); void setCacheVersion( int version ); bool loadCacheVersion( KoXmlElement &element, XMLLoaderObject &status ); void saveCacheVersion( QDomElement &element ) const; /// A calendar can be local to this project, or /// defined externally and shared with other projects bool isShared() const; /// Set calendar to be local if on = false, or shared if on = true void setShared(bool on); #ifdef HAVE_KHOLIDAYS bool isHoliday(QDate date) const; KHolidays::HolidayRegion *holidayRegion() const; void setHolidayRegion(const QString &code); QString holidayRegionCode() const; QStringList holidayRegionCodes() const; #endif Q_SIGNALS: void changed( Calendar* ); void changed( CalendarDay* ); void changed( TimeInterval* ); void weekdayToBeAdded( CalendarDay *day, int index ); void weekdayAdded( CalendarDay *day ); void weekdayToBeRemoved( CalendarDay *day ); void weekdayRemoved( CalendarDay *day ); void dayToBeAdded( CalendarDay *day, int index ); void dayAdded( CalendarDay *day ); void dayToBeRemoved( CalendarDay *day ); void dayRemoved( CalendarDay *day ); void workIntervalToBeAdded( CalendarDay*, TimeInterval*, int index ); void workIntervalAdded( CalendarDay*, TimeInterval* ); void workIntervalToBeRemoved( CalendarDay*, TimeInterval* ); void workIntervalRemoved( CalendarDay*, TimeInterval* ); protected: void init(); const Calendar ©(const Calendar &calendar); /** * Returns the amount of 'worktime' that can be done on * the @p date between the times @p start and @p start + @p length. * The date and times are in timespecification @p spec. * If @p sch is not 0, the schedule is checked for availability. */ Duration effort(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the amount of 'worktime' that can be done in the * interval from @p start to @p end * If @p sch is not 0, the schedule is checked for availability. */ Duration effort(const QDateTime &start, const QDateTime &end, Schedule *sch=0) const; /** * Returns the first 'work interval' on date for the interval * starting at @p start and ending at @p start + @p length. * If no 'work interval' exists, returns a null interval. * You can also use @ref hasInterval() to check if a 'work interval' exists. * The date and times are in timespecification spec. * If @p sch is not 0, the schedule is checked for availability. */ TimeInterval firstInterval(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the first 'work interval' for the interval * starting at @p start and ending at @p end. * If no 'work interval' exists, returns an interval with invalid DateTime. */ DateTimeInterval firstInterval( const QDateTime &start, const QDateTime &end, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval on date, starting at @p start and ending at @p start + @p length. * If @p sch is not 0, the schedule is checked for availability. */ bool hasInterval(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the work intervals in the interval from @p start to @p end * Sets the load of each interval to @p load */ AppointmentIntervalList workIntervals(const QDateTime &start, const QDateTime &end, double load) const; /** * Find the first available time backwards from @p time. Search until @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableBefore(const QDateTime &time, const QDateTime &limit, Schedule *sch = 0); private: QString m_name; Calendar *m_parent; Project *m_project; bool m_deleted; QString m_id; QString m_parentId; QList m_days; CalendarWeekdays *m_weekdays; QList m_calendars; QTimeZone m_timeZone; bool m_default; // this is the default calendar, only used for save/load bool m_shared; #ifdef HAVE_KHOLIDAYS KHolidays::HolidayRegion *m_region; QString m_regionCode; #endif int m_cacheversion; // incremented every time a calendar is changed friend class Project; int m_blockversion; // don't update if true #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; class KPLATOKERNEL_EXPORT StandardWorktime { public: explicit StandardWorktime( Project *project = 0 ); explicit StandardWorktime(StandardWorktime* worktime); ~StandardWorktime(); /// Set Project void setProject( Project *project ) { m_project = project; } /// The work time of a normal year. Duration durationYear() const { return m_year; } /// The work time of a normal year. double year() const { return m_year.toDouble(Duration::Unit_h); } /// Set the work time of a normal year. void setYear(const Duration year) { m_year = year; } /// Set the work time of a normal year. void setYear(double hours) { m_year = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal month Duration durationMonth() const { return m_month; } /// The work time of a normal month double month() const { return m_month.toDouble(Duration::Unit_h); } /// Set the work time of a normal month void setMonth(const Duration month) { m_month = month; } /// Set the work time of a normal month void setMonth(double hours) { m_month = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal week Duration durationWeek() const { return m_week; } /// The work time of a normal week double week() const { return m_week.toDouble(Duration::Unit_h); } /// Set the work time of a normal week void setWeek(const Duration week) { m_week = week; } /// Set the work time of a normal week void setWeek(double hours) { m_week = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal day Duration durationDay() const { return m_day; } /// The work time of a normal day double day() const { return m_day.toDouble(Duration::Unit_h); } /// Set the work time of a normal day void setDay(const Duration day) { m_day = day; changed(); } /// Set the work time of a normal day void setDay(double hours) { m_day = Duration(hours, Duration::Unit_h); changed(); } QList scales() const; bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; void changed(); protected: void init(); private: Project *m_project; Duration m_year; Duration m_month; Duration m_week; Duration m_day; }; } //KPlato namespace #endif diff --git a/plan/src/libs/kernel/kpttask.cpp b/plan/src/libs/kernel/kpttask.cpp index b15ee849e4d..bb3652104c4 100644 --- a/plan/src/libs/kernel/kpttask.cpp +++ b/plan/src/libs/kernel/kpttask.cpp @@ -1,3836 +1,3836 @@ /* 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. */ #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 #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")) { // 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_workPackage.saveXML(me); completion().saveXML( me ); 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); } } } if ( ! m_requests.isEmpty() ) { m_requests.save(me); } m_documents.save( 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 ); } } for (int i=0; isave(me); } } 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 } 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; foreach ( QDate d, m_entries.keys() ) { if ( d <= date ) { x = m_entries[ d ]->percentFinished; } if ( d >= date ) { break; } } return x; } Duration Completion::remainingEffort() const { return m_entries.isEmpty() ? Duration::zeroDuration : m_entries.values().last()->remainingEffort; } Duration Completion::remainingEffort( QDate date ) const { Duration x; foreach ( const QDate &d, m_entries.keys() ) { if ( d <= date ) { x = m_entries[ d ]->remainingEffort; } if ( d >= date ) { break; } } return x; } Duration Completion::actualEffort() const { Duration eff; if ( m_entrymode == EnterEffortPerResource ) { foreach( const UsedEffort *ue, m_usedEffort ) { foreach ( const QDate &d, ue->actualEffortMap().keys() ) { eff += ue->actualEffortMap()[ d ].effort(); } } } else if ( ! m_entries.isEmpty() ) { eff = m_entries.values().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 knowon 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: { - QPair dates = actualStartEndDates(); + 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(); + 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( 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.values().last()->note; } void Completion::setNote( const QString &str ) { if ( ! m_entries.isEmpty() ) { m_entries.values().last()->note = str; changed(); } } -QPair Completion::actualStartEndDates() const +std::pair Completion::actualStartEndDates() const { - QPair p; + std::pair p; foreach ( const Resource *r, m_usedEffort.keys() ) { if ( ! m_usedEffort[ r ]->actualEffortMap().isEmpty() ) { QDate d = m_usedEffort[ r ]->actualEffortMap().keys().first(); if ( ! p.first.isValid() || d < p.first ) p.first = d; d = m_usedEffort[ r ]->actualEffortMap().keys().last(); if ( ! p.second.isValid() || d > p.second ) p.second = d; } } return p; } double Completion::actualCost( QDate date ) const { //debugPlan<normalRate(); double oc = r->overtimeRate(); if ( m_usedEffort[ r ]->actualEffortMap().contains( date ) ) { UsedEffort::ActualEffort a = m_usedEffort[ r ]->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; foreach ( const Resource *r, m_usedEffort.keys() ) { c += actualCost( r ); } 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; foreach ( const Resource *r, m_usedEffort.keys() ) { //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().keys().first() ) { start = lst.last().keys().first(); } if ( ! end.isValid() || end < lst.last().keys().last() ) { end = lst.last().keys().last(); } } 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 ) { foreach ( const QDate &d, value.actualEffortMap().keys() ) { setEffort( d, value.actualEffortMap()[ d ] ); } } void Completion::UsedEffort::setEffort( QDate date, const ActualEffort &value ) { m_actual.insert( date, value ); } Duration Completion::UsedEffort::effortTo( QDate date ) const { Duration eff; foreach ( const QDate &d, m_actual.keys() ) { if ( d > date ) { break; } eff += m_actual[ d ].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( false ), 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/plan/src/libs/kernel/kpttask.h b/plan/src/libs/kernel/kpttask.h index b005746b30f..2ce16c8cd52 100644 --- a/plan/src/libs/kernel/kpttask.h +++ b/plan/src/libs/kernel/kpttask.h @@ -1,761 +1,761 @@ /* 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 "kplatokernel_export.h" #include "kptnode.h" #include "kptglobal.h" #include "kptdatetime.h" #include "kptduration.h" #include "kptresource.h" #include #include -#include +#include /// The main namespace. namespace KPlato { class Completion; /** * The Completion class holds information about the tasks progress. */ class KPLATOKERNEL_EXPORT Completion { public: class KPLATOKERNEL_EXPORT UsedEffort { public: - class KPLATOKERNEL_EXPORT ActualEffort : public QPair + class KPLATOKERNEL_EXPORT ActualEffort : public std::pair { public: explicit ActualEffort( KPlato::Duration ne = Duration::zeroDuration, KPlato::Duration oe = Duration::zeroDuration ) - : QPair( ne, oe ) + : std::pair( ne, oe ) {} ActualEffort( const ActualEffort &e ) - : QPair( e.first, e.second ) + : 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; 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 KPLATOKERNEL_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 QMap 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; - QPair actualStartEndDates() 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 KPLATOKERNEL_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 appointements or requests /// merged with resources added to completion QList fetchResources(); /// Return a list of resources fetched from the appointements 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 KPLATOKERNEL_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 KPLATOKERNEL_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; /// 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, useing 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 reosurce 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 fullfil 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 fullfil the estimated effort. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ virtual bool effortMetError( long id = CURRENTSCHEDULE ) 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( Node *node, int row ); void workPackageAdded( Node *node ); void workPackageToBeRemoved( Node *node, int row ); void workPackageRemoved( 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 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 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 KPLATOKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae ); #endif #endif diff --git a/plan/src/main.cpp b/plan/src/main.cpp index c73981b04f1..275cb6a34b4 100644 --- a/plan/src/main.cpp +++ b/plan/src/main.cpp @@ -1,57 +1,57 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander 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. */ #include "kptaboutdata.h" #include "kptmaindocument.h" #include #include #include #include -extern "C" KPLATO_EXPORT int kdemain( int argc, char **argv ) +extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv ) { /** * Disable debug output by default, only log warnings. * Debug logs can be controlled by the environment variable QT_LOGGING_RULES. * * For example, to get full debug output, run the following: * QT_LOGGING_RULES="calligra.*=true" calligraplan * * See: http://doc.qt.io/qt-5/qloggingcategory.html */ QLoggingCategory::setFilterRules("calligra.*.debug=false\n" "calligra.*.warning=true"); QApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); KoApplication app(PLAN_MIME_TYPE, QStringLiteral("calligraplan"), KPlato::newAboutData, argc, argv); // Migrate data from kde4 to kf5 locations Calligra2Migration m("calligraplan", "plan"); m.setConfigFiles(QStringList() << QStringLiteral("planrc")); m.setUiFiles(QStringList() << QStringLiteral("plan.rc") << QStringLiteral("plan_readonly.rc")); m.migrate(); if (!app.start()) { return 1; } return app.exec(); } diff --git a/plan/src/plugins/schedulers/tj/CMakeLists.txt b/plan/src/plugins/schedulers/tj/CMakeLists.txt index d731fb213c9..8f29a2f66b1 100644 --- a/plan/src/plugins/schedulers/tj/CMakeLists.txt +++ b/plan/src/plugins/schedulers/tj/CMakeLists.txt @@ -1,61 +1,61 @@ add_definitions(-DTRANSLATION_DOMAIN=\"calligraplan_scheduler_tj\") set(LIBTJ_INCLUDE_DIR taskjuggler) include_directories(${LIBTJ_INCLUDE_DIR} ${PLANODF_INCLUDES} ${PLANKERNEL_INCLUDES}) #add_subdirectory( taskjuggler ) if(BUILD_TESTING) add_subdirectory( tests ) endif() set ( TJScheduler_SRCS PlanTJPlugin.cpp PlanTJScheduler.cpp taskjuggler/Allocation.cpp taskjuggler/CoreAttributes.cpp taskjuggler/CoreAttributesList.cpp taskjuggler/Project.cpp taskjuggler/Task.cpp taskjuggler/TaskDependency.cpp taskjuggler/TaskList.cpp taskjuggler/TaskScenario.cpp taskjuggler/Resource.cpp taskjuggler/ResourceList.cpp taskjuggler/Scenario.cpp taskjuggler/ScenarioList.cpp taskjuggler/Shift.cpp taskjuggler/ShiftList.cpp taskjuggler/ShiftSelection.cpp taskjuggler/ShiftSelectionList.cpp taskjuggler/VacationList.cpp taskjuggler/TjMessageHandler.cpp taskjuggler/Utility.cpp # taskjuggler/XMLFile.cpp # taskjuggler/ParserElement.cpp # taskjuggler/ParserNode.cpp # taskjuggler/ParserTreeContext.cpp taskjuggler/Interval.cpp ) # TODO: plugin should not be SHARED, but MODULE. Needs to be SHARED because tests link to it -> fix with util lib/objects add_library(plantjscheduler SHARED ${TJScheduler_SRCS} ) # calligraplan_scheduler_desktop_to_json(plantjscheduler plantjscheduler.desktop) if(${KF5_VERSION} VERSION_LESS "5.16.0") kcoreaddons_desktop_to_json(plantjscheduler plantjscheduler.desktop) else() kcoreaddons_desktop_to_json(plantjscheduler plantjscheduler.desktop SERVICE_TYPES ${PLAN_SOURCE_DIR}/libs/kernel/plan_schedulerplugin.desktop ) endif() # TODO: only export symbols for tests, not release generate_export_header(plantjscheduler BASE_NAME kplatotj) - +add_definitions(-Dplantjscheduler_EXPORTS) target_link_libraries( plantjscheduler kplatokernel ) set_target_properties( plantjscheduler PROPERTIES DEFINE_SYMBOL MAKE_KPLATOTJ_LIB ) install( TARGETS plantjscheduler DESTINATION ${PLUGIN_INSTALL_DIR}/calligraplan/schedulers ) diff --git a/plan/src/plugins/schedulers/tj/taskjuggler/Utility.cpp b/plan/src/plugins/schedulers/tj/taskjuggler/Utility.cpp index ab7bf5cfcbb..b46e369d9b9 100644 --- a/plan/src/plugins/schedulers/tj/taskjuggler/Utility.cpp +++ b/plan/src/plugins/schedulers/tj/taskjuggler/Utility.cpp @@ -1,957 +1,962 @@ /* * Utility.cpp - TaskJuggler * * Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger * Copyright (c) 2011 by Dag Andersen * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * $Id$ */ #include "Utility.h" #include #include #include #include #include #include #include #include "TjMessageHandler.h" #include "tjlib-internal.h" #if defined(__SVR4) && defined(__sun) int setenv(const char* var, const char* val, int ignore); int unsetenv (const char *var); #endif +#if _MSC_VER >= 1900 +#define tzname _tzname +#define tzset _tzset +#endif + namespace TJ { static QMap TZDict; static bool TZDictReady = false; static QString UtilityError; /* localtime() calls are fairly expensive, so we implement a hashtable based * cache to avoid redundant calls for the same value. Changing the timezone * invalidates the cache though. */ class LtHashTabEntry { public: LtHashTabEntry() : tms( 0 ), next( 0 ) {} ~LtHashTabEntry() { /*qDebug()<<"~LtHashTabEntry";*/ delete tms; } time_t t; struct tm* tms; LtHashTabEntry* next; }; static long LTHASHTABSIZE; static LtHashTabEntry** LtHashTab = 0; bool isRichText(const QString& str) { /* This function tries to guess whether a string is a rich-text string or * not. It looks for xml tags marks and does a simple validation on them. */ bool hasTags = false; bool inTag = false; for (int i = 0; i < str.length(); ++i) { if (str[i] == '<') { if (inTag) return false; inTag = hasTags = true; } else if (str[i] == '>') { if (!inTag) return false; inTag = false; } } return hasTags && !inTag; } const char* timezone2tz(const char* tzone) { if (!TZDictReady) { // TZDict.setAutoDelete(false); // Let's start with generic timezones TZDict.insert("+1300", "GMT-13:00"); TZDict.insert("+1200", "GMT-12:00"); TZDict.insert("+1100", "GMT-11:00"); TZDict.insert("+1000", "GMT-10:00"); TZDict.insert("+0900", "GMT-9:00"); TZDict.insert("+0800", "GMT-8:00"); TZDict.insert("+0700", "GMT-7:00"); TZDict.insert("+0600", "GMT-6:00"); TZDict.insert("+0500", "GMT-5:00"); TZDict.insert("+0400", "GMT-4:00"); TZDict.insert("+0300", "GMT-3:00"); TZDict.insert("+0200", "GMT-2:00"); TZDict.insert("+0100", "GMT-1:00"); TZDict.insert("+0000", "GMT-0:00"); TZDict.insert("-0100", "GMT+1:00"); TZDict.insert("-0200", "GMT+2:00"); TZDict.insert("-0300", "GMT+3:00"); TZDict.insert("-0400", "GMT+4:00"); TZDict.insert("-0500", "GMT+5:00"); TZDict.insert("-0600", "GMT+6:00"); TZDict.insert("-0700", "GMT+7:00"); TZDict.insert("-0800", "GMT+8:00"); TZDict.insert("-0900", "GMT+9:00"); TZDict.insert("-1000", "GMT+10:00"); TZDict.insert("-1100", "GMT+11:00"); TZDict.insert("-1200", "GMT+12:00"); // Now some conveniance timezones. There will be more in the future. TZDict.insert("PST", "GMT+8:00"); TZDict.insert("PDT", "GMT+7:00"); TZDict.insert("MST", "GMT+7:00"); TZDict.insert("MDT", "GMT+6:00"); TZDict.insert("CST", "GMT+6:00"); TZDict.insert("CDT", "GMT+5:00"); TZDict.insert("EST", "GMT+5:00"); TZDict.insert("EDT", "GMT+4:00"); TZDict.insert("GMT", "GMT"); TZDict.insert("UTC", "GMT"); TZDict.insert("CET", "GMT-1:00"); TZDict.insert("CEDT", "GMT-2:00"); TZDictReady = true; } return TZDict[tzone]; } void initUtility(long dictSize) { if (LtHashTab) exitUtility(); /* Find a prime number that is equal or bigger than dictSize. */ for (long i = 2; i < (dictSize / 2); i++) if (dictSize % i == 0) { dictSize++; i = 1; } LtHashTab = new LtHashTabEntry*[LTHASHTABSIZE = dictSize]; for (long i = 0; i < LTHASHTABSIZE; ++i) LtHashTab[i] = 0; } void exitUtility() { qDebug()<<"exitUtility:"<next; delete htep; htep = tmp; } delete [] LtHashTab; LtHashTab = 0; } bool setTimezone(const char* tZone) { UtilityError.clear(); - if (setenv("TZ", tZone, 1) < 0) + if (!qputenv("TZ", tZone)) qFatal("Ran out of space in environment section while " "setting timezone."); /* To validate the tZone value we call tzset(). It will convert the zone * into a three-letter acronym in case the tZone value is good. If not, it * will just copy the wrong value to tzname[0] (glibc < 2.5) or fall back * to UTC. */ tzset(); if (timezone2tz(tZone) == 0 && (strcmp(tzname[0], tZone) == 0 || (strcmp(tZone, "UTC") != 0 && strcmp(tzname[0], "UTC") == 0))) { qDebug("1: %s, 2: %s", tzname[0], tzname[1]); UtilityError = QString(QString("Illegal timezone '%1'")).arg(tZone); return false; } if (!LtHashTab) return true; for (long i = 0; i < LTHASHTABSIZE; ++i) { for (LtHashTabEntry* htep = LtHashTab[i]; htep; ) { LtHashTabEntry* tmp = htep->next; delete htep->tms; htep = tmp; } if (LtHashTab[i]) LtHashTab[i] = 0; } return true; } const struct tm * clocaltime(const time_t* t) { /* In some cases we haven't initialized the module yet. So we do not use * the cache. */ time_t tt = *t < 0 ? 0 : *t; if (!LtHashTab) return localtime(&tt); long index = tt % LTHASHTABSIZE; if (LtHashTab[index]) for (LtHashTabEntry* htep = LtHashTab[index]; htep; htep = htep->next) if (htep->t == tt) return htep->tms; LtHashTabEntry* htep = new LtHashTabEntry; htep->next = LtHashTab[index]; htep->t = tt; htep->tms = new struct tm; memcpy(htep->tms, localtime(&tt), sizeof(struct tm)); LtHashTab[index] = htep; return htep->tms; } const QString& getUtilityError() { return UtilityError; } QString monthAndYear(time_t t) { const struct tm* tms = clocaltime(&t); static char s[32]; strftime(s, sizeof(s), "%b %Y", tms); return QString::fromLocal8Bit(s); } QString shortMonthName(int mon) { struct tm tms; tms.tm_mday = 1; tms.tm_mon = mon; tms.tm_year = 2000; static char s[32]; strftime(s, sizeof(s), "%b", &tms); return QString::fromLocal8Bit(s); } bool isWeekend(time_t t) { const struct tm* tms = clocaltime(&t); return (tms->tm_wday < 1 || tms->tm_wday > 5); } int daysLeftInMonth(time_t t) { int left = 0; const struct tm* tms = clocaltime(&t); for (int m = tms->tm_mon; tms->tm_mon == m; ) { left++; t = sameTimeNextDay(t); tms = clocaltime(&t); } return left; } int weeksLeftInMonth(time_t t) { int left = 0; const struct tm* tms = clocaltime(&t); for (int m = tms->tm_mon; tms->tm_mon == m; ) { left++; t = sameTimeNextWeek(t); tms = clocaltime(&t); } return left; } int monthLeftInYear(time_t t) { int left = 0; const struct tm* tms = clocaltime(&t); for (int m = tms->tm_year; tms->tm_year == m; ) { left++; t = sameTimeNextMonth(t); tms = clocaltime(&t); } return left; } int quartersLeftInYear(time_t t) { int left = 0; const struct tm* tms = clocaltime(&t); for (int m = tms->tm_year; tms->tm_year == m; ) { left++; t = sameTimeNextQuarter(t); tms = clocaltime(&t); } return left; } int daysBetween(time_t t1, time_t t2) { int days = 0; // TODO: Very slow!!! for (time_t t = t1; t < t2; t = sameTimeNextDay(t)) days++; return days; } int weeksBetween(time_t t1, time_t t2) { int days = 0; // TODO: Very slow!!! for (time_t t = t1; t < t2; t = sameTimeNextWeek(t)) days++; return days; } int monthsBetween(time_t t1, time_t t2) { int months = 0; // TODO: Very slow!!! for (time_t t = t1; t < t2; t = sameTimeNextMonth(t)) months++; return months; } int quartersBetween(time_t t1, time_t t2) { int quarters = 0; // TODO: Very slow!!! for (time_t t = t1; t < t2; t = sameTimeNextQuarter(t)) quarters++; return quarters; } int secondsOfDay(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_sec + tms->tm_min * 60 + tms->tm_hour * 3600; } int hourOfDay(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_hour; } int dayOfMonth(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_mday; } int weekOfYear(time_t t, bool beginOnMonday) { /* The ISO 8601:1988 week number of the current year as a decimal * number, range 1 to 53, where week 1 is the first week that has at * least 4 days in the current year, and with Monday as the first day * of the week. This is also compliant with DIN 1355. */ uint week = 0; uint weekday1Jan = dayOfWeek(beginOfYear(t), beginOnMonday); const struct tm* tms = clocaltime(&t); int days = tms->tm_yday; if (weekday1Jan > 3) days = days - (7 - weekday1Jan); else days = days + weekday1Jan; if (days < 0) if ((weekday1Jan == 4) || (dayOfWeek(beginOfYear(beginOfYear(t) - 1), beginOnMonday) == 3)) week = 53; else week = 52; else week = days / 7 + 1; if ((days > 360) && (week > 52)) { if (weekday1Jan == 3) week = 53; else if (dayOfWeek(sameTimeNextYear(beginOfYear(t)), beginOnMonday) == 4) week = 53; else week = 1; } return week; } int monthOfWeek(time_t t, bool beginOnMonday) { const struct tm* tms = clocaltime(&t); int tm_mon = tms->tm_mon; int tm_mday = tms->tm_mday; int lastDayOfMonth = dayOfMonth(beginOfMonth(sameTimeNextMonth(t)) - 1); if (tm_mday < 4) { if (dayOfWeek(t, beginOnMonday) - tm_mday >= 3) { if (tm_mon == 0) { return 12; } else { return tm_mon; } } } else if (tm_mday > lastDayOfMonth - 4) { if (tm_mday - dayOfWeek(t, beginOnMonday) > lastDayOfMonth - 4) { if (tm_mon == 11) { return 1; } else { return tm_mon + 2; } } } return tm_mon + 1; } int monthOfYear(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_mon + 1; } int quarterOfYear(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_mon / 3 + 1; } int dayOfWeek(time_t t, bool beginOnMonday) { const struct tm* tms = clocaltime(&t); if (beginOnMonday) return tms->tm_wday ? tms->tm_wday - 1 : 6; else return tms->tm_wday; } QString dayOfWeekName(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[64]; strftime(buf, 63, "%A", tms); return QString::fromLocal8Bit(buf); } int dayOfYear(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_yday + 1; } int year(time_t t) { const struct tm* tms = clocaltime(&t); return tms->tm_year + 1900; } int yearOfWeek(time_t t, bool beginOnMonday) { const struct tm* tms = clocaltime(&t); int tm_year = tms->tm_year; int lastDayOfYear = dayOfYear(beginOfYear(sameTimeNextYear(t)) - 1); if (dayOfYear(t) < 4) { if (dayOfWeek(t, beginOnMonday) - dayOfYear(t) >= 3) return 1900 + tm_year - 1; } else if (dayOfYear(t) > lastDayOfYear - 4) { if (dayOfYear(t) - dayOfWeek(t, beginOnMonday) > lastDayOfYear - 4) return 1900 + tm_year + 1; } return 1900 + tm_year; } time_t beginOfHour(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_sec = tmc.tm_min = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t midnight(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t beginOfWeek(time_t t, bool beginOnMonday) { const struct tm* tms; for (tms = clocaltime(&t) ; tms->tm_wday != (beginOnMonday ? 1 : 0); ) { t = sameTimeYesterday(t); tms = clocaltime(&t); } struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t beginOfMonth(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mday = 1; tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t beginOfQuarter(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mon = (tmc.tm_mon / 3) * 3; tmc.tm_mday = 1; tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t beginOfYear(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mon = 0; tmc.tm_mday = 1; tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0; tmc.tm_isdst = -1; return mktime(&tmc); } time_t hoursLater(int h, time_t t) { // I hope this is correct under all circumstances. return t + h * ONEHOUR; } time_t sameTimeNextDay(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mday++; tmc.tm_isdst = -1; if (mktime(&tmc) == -1) qFatal("Error at %s", time2ISO(t).toLatin1().constData()); return mktime(&tmc); } time_t sameTimeYesterday(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mday--; tmc.tm_isdst = -1; return mktime(&tmc); } time_t sameTimeNextWeek(time_t t) { const struct tm* tms = clocaltime(&t); int weekday = tms->tm_wday; do { t = sameTimeNextDay(t); tms = clocaltime(&t); } while (tms->tm_wday != weekday); return t; } time_t sameTimeLastWeek(time_t t) { const struct tm* tms = clocaltime(&t); int weekday = tms->tm_wday; do { t = sameTimeYesterday(t); tms = clocaltime(&t); } while (tms->tm_wday != weekday); return t; } time_t sameTimeNextMonth(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mon++; tmc.tm_isdst = -1; return mktime(&tmc); } time_t sameTimeNextQuarter(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_mon += 3; tmc.tm_isdst = -1; return mktime(&tmc); } time_t sameTimeNextYear(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_year++; tmc.tm_isdst = -1; return mktime(&tmc); } time_t sameTimeLastYear(time_t t) { const struct tm* tms = clocaltime(&t); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_year--; tmc.tm_isdst = -1; return mktime(&tmc); } QString time2ISO(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[128]; strftime(buf, 127, "%Y-%m-%d %H:%M:%S %Z", tms); return QString::fromLocal8Bit(buf); } QString time2tjp(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[128]; strftime(buf, 127, "%Y-%m-%d-%H:%M:%S-%z", tms); return QString::fromLocal8Bit(buf); } QString time2user(time_t t, const QString& timeFormat, bool localtime) { if (t == 0) return QString("undefined"); const struct tm* tms; if (localtime) tms = clocaltime(&t); else tms = gmtime(&t); static char buf[128]; strftime(buf, 127, timeFormat.toLocal8Bit(), tms); return QString::fromLocal8Bit(buf); } QString time2time(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[128]; strftime(buf, 127, "%H:%M %Z", tms); return QString::fromLocal8Bit(buf); } QString time2date(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[128]; strftime(buf, 127, "%Y-%m-%d", tms); return QString::fromLocal8Bit(buf); } QString time2weekday(time_t t) { const struct tm* tms = clocaltime(&t); static char buf[128]; strftime(buf, 127, "%A", tms); return QString::fromLocal8Bit(buf); } time_t addTimeToDate(time_t day, time_t hour) { day = midnight(day); const struct tm* tms = clocaltime(&day); struct tm tmc; memcpy(&tmc, tms, sizeof(struct tm)); tmc.tm_hour = hour / (60 * 60); tmc.tm_min = (hour / 60) % 60; tmc.tm_sec = hour % 60; tmc.tm_isdst = -1; return mktime(&tmc); } time_t date2time(const QString& date) { UtilityError.clear(); int y, m, d, hour, min, sec; char tZone[64] = ""; std::string savedTZ; bool restoreTZ = false; if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d:%d-%s", &y, &m, &d, &hour, &min, &sec, tZone) == 7 || (sec = 0) || // set sec to 0 sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d-%s", &y, &m, &d, &hour, &min, tZone) == 6) { const char* tz; if ((tz = getenv("TZ")) != 0) { savedTZ = tz; } if ((tz = timezone2tz(tZone)) == 0) { UtilityError = QString("Illegal timezone %1").arg(tZone); return 0; } else { - if (setenv("TZ", tz, 1) < 0) + if (!qputenv("TZ", tz)) qFatal("date2time: Ran out of space in environment section."); restoreTZ = true; } } else if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d:%d", &y, &m, &d, &hour, &min, &sec) == 6) tZone[0] = '\0'; else if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5) { sec = 0; tZone[0] = '\0'; } else if (sscanf(date.toLocal8Bit(), "%d-%d-%d", &y, &m, &d) == 3) { tZone[0] = '\0'; hour = min = sec = 0; } else { qFatal("%s", QString("Illegal date: %1").arg(date).toLocal8Bit().constData()); return 0; } if (y < 1970) { UtilityError = QString("Year must be larger than 1969"); return 0; } if (m < 1 || m > 12) { UtilityError = QString("Month must be between 1 and 12"); return 0; } if (d < 1 || d > 31) { UtilityError = QString("Day must be between 1 and 31"); return 0; } if (hour < 0 || hour > 23) { UtilityError = QString("Hour must be between 0 and 23"); return 0; } if (min < 0 || min > 59) { UtilityError = QString("Minutes must be between 0 and 59"); return 0; } if (sec < 0 || sec > 59) { UtilityError = QString("Seconds must be between 0 and 59"); return 0; } -#if defined(Q_WS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun)) +#if defined(Q_OS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun)) struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1 }; #else struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 }; #endif time_t localTime = mktime(&t); if (restoreTZ) { if (!savedTZ.empty()) { - if (setenv("TZ", savedTZ.c_str(), 1) < 0) + if (!qputenv("TZ", savedTZ.c_str())) qFatal("date2time: Ran out of space in environment section."); } else - unsetenv("TZ"); + qunsetenv("TZ"); } return localTime; } QString formatTime(time_t t) { return QLocale().toString(QDateTime::fromTime_t(t), QLocale::ShortFormat); } QDate time2qdate(time_t t) { return QDate(year(t), monthOfYear(t), dayOfMonth(t)); } time_t qdate2time(const QDate& d) { -#if defined(Q_WS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun)) +#if defined(Q_OS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun)) struct tm t = { 0, 0, 0, d.day(), d.month() - 1, d.year() - 1900, 0, 0, -1 }; #else struct tm t = { 0, 0, 0, d.day(), d.month() - 1, d.year() - 1900, 0, 0, -1, 0, 0 }; #endif return mktime(&t); } #if defined(__SVR4) && defined(__sun) /* * Note: a proper implementation of a "setenv" function for Solaris * would take a map where the "variable-name" is linked to a * pointer. This would allow freeing the memory allocated here * (a kind of garbage collection). */ int setenv(const char* var, const char* val, int ignore) { int varLen = strlen(var); int valLen = strlen(val); char *buffer = NULL; if ((buffer = static_cast(malloc(varLen + valLen + 2))) == NULL) return -1; sprintf (buffer, "%s=%s", var, val); return putenv(buffer) ? -1 : 0; } int unsetenv (const char *var) { return 0; /* SKIP */ } #endif } // namespace TJ diff --git a/plan/src/workpackage/main.cpp b/plan/src/workpackage/main.cpp index 1fd5db976fa..d15db7218e1 100644 --- a/plan/src/workpackage/main.cpp +++ b/plan/src/workpackage/main.cpp @@ -1,54 +1,52 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander 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. */ -#include "kplatowork_export.h" - #include "commandlineparser.h" #include #include #include #include -extern "C" KPLATOWORK_EXPORT int kdemain( int argc, char **argv ) +extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv ) { QApplication app(argc, argv); #ifdef Q_OS_MACOS // app.applicationName() will return "Plan Work" because of the nice name // set in the Info.plist. DBus doesn't like the resulting space in the // service name, so reset the application name: app.setApplicationName("calligraplanwork"); #endif KDBusService service(KDBusService::Unique); // we come here only once... // Migrate data from kde4 to kf5 locations Calligra2Migration m("calligraplanwork", "planwork"); m.setConfigFiles(QStringList() << QStringLiteral("planworkrc")); m.setUiFiles(QStringList() << QStringLiteral("planwork.rc") << QStringLiteral("planwork_readonly.rc") << QStringLiteral("planworkui.rc")); m.migrate(); CommandLineParser cmd; QObject::connect(&service, &KDBusService::activateRequested, &cmd, &CommandLineParser::handleActivateRequest); cmd.handleCommandLine(QDir::current()); return app.exec(); }