diff --git a/src/libs/kernel/kptcalendar.cpp b/src/libs/kernel/kptcalendar.cpp index ee200a80..aafb0ec2 100644 --- a/src/libs/kernel/kptcalendar.cpp +++ b/src/libs/kernel/kptcalendar.cpp @@ -1,1709 +1,1763 @@ /* 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. */ // clazy:excludeall=qstring-arg #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.key(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().constLast(); 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 > 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 << 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 + +QString stateToString(int state) +{ + const QStringList lst = QStringList() << "U" << "N" << "W"; + return lst.value(state); +} +QDebug operator<<(QDebug dbg, KPlato::Calendar *c) +{ + if (!c) { + return dbg << "Calendar[0x0]"; + } + dbg << "Calendar[" << c->name(); + for (int i = 1; i <= 7; ++i) { + if (c->weekday(i)) { + dbg << endl << '\t' << i << ':' << c->weekday(i); + } + } + foreach(const KPlato::CalendarDay *day, c->days()) { + dbg << endl << '\t'<< day; + } + dbg << endl << ']'; + return dbg; +} + +QDebug operator<<(QDebug dbg, KPlato::CalendarWeekdays *w) +{ + if (!w) { + return dbg << "Weekdays[0x0]"; + } + dbg << "Weekdays:" << w->weekdayMap(); + return dbg; +} + +QDebug operator<<(QDebug dbg, KPlato::CalendarDay *day) +{ + if (!day) { + return dbg << "Day[0x0]"; + } + dbg << "Day[" << stateToString(day->state()); + if (day->date().isValid()) { + dbg << day->date(); + } + for(int i; i < day->numIntervals(); ++i) { + dbg << *(day->timeIntervals().at(i)); + } + dbg << "]"; + return dbg; +} + +QDebug operator<<(QDebug dbg, KPlato::StandardWorktime *wt) +{ + dbg << "Standard"; + return dbg; +} diff --git a/src/libs/kernel/kptcalendar.h b/src/libs/kernel/kptcalendar.h index 8678538e..e8eb9139 100644 --- a/src/libs/kernel/kptcalendar.h +++ b/src/libs/kernel/kptcalendar.h @@ -1,694 +1,700 @@ /* 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 "plankernel_export.h" #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 Project; class Schedule; class XMLLoaderObject; class AppointmentIntervalList; class PLANKERNEL_EXPORT DateTimeInterval : public std::pair { public: DateTimeInterval() : std::pair() {} DateTimeInterval( const DateTime &t1, const DateTime &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("''"), 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 PLANKERNEL_EXPORT TimeInterval : public std::pair { public: TimeInterval() : std::pair( QTime(), -1 ) {} explicit TimeInterval( std::pair value ) : std::pair( value ) { init(); } TimeInterval( QTime start, int length ) : std::pair( start, length ) { init(); } TimeInterval( const TimeInterval &value ) : 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 PLANKERNEL_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 PLANKERNEL_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 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(KPlato::Calendar*); void changed(KPlato::CalendarDay*); void changed(KPlato::TimeInterval*); void weekdayToBeAdded(KPlato::CalendarDay *day, int index); void weekdayAdded(KPlato::CalendarDay *day); void weekdayToBeRemoved(KPlato::CalendarDay *day); void weekdayRemoved(KPlato::CalendarDay *day); void dayToBeAdded(KPlato::CalendarDay *day, int index); void dayAdded(KPlato::CalendarDay *day); void dayToBeRemoved(KPlato::CalendarDay *day); void dayRemoved(KPlato::CalendarDay *day); void workIntervalToBeAdded(KPlato::CalendarDay*, KPlato::TimeInterval*, int index); void workIntervalAdded(KPlato::CalendarDay*, KPlato::TimeInterval*); void workIntervalToBeRemoved(KPlato::CalendarDay*, KPlato::TimeInterval*); void workIntervalRemoved(KPlato::CalendarDay*, KPlato::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 PLANKERNEL_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 +PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::Calendar *c); +PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::CalendarWeekdays *w); +PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::CalendarDay *day); + +PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::StandardWorktime *wt); + #endif diff --git a/src/libs/kernel/kptresource.cpp b/src/libs/kernel/kptresource.cpp index 12f5809a..69f1c29c 100644 --- a/src/libs/kernel/kptresource.cpp +++ b/src/libs/kernel/kptresource.cpp @@ -1,2656 +1,2665 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2004-2007, 2012 Dag Andersen Copyright (C) 2016 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptresource.h" #include "kptlocale.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptdatetime.h" #include "kptcalendar.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "kptdebug.h" #include #include #include namespace KPlato { ResourceGroup::ResourceGroup() : QObject( 0 ), m_blockChanged(false), m_shared(false) { m_project = 0; m_type = Type_Work; //debugPlan<<"("<unregister( this ); } while (!m_resources.isEmpty()) { delete m_resources.takeFirst(); } //debugPlan<<"("<m_project; //Don't copy m_id = group->m_id; m_type = group->m_type; m_name = group->m_name; } void ResourceGroup::blockChanged(bool on) { m_blockChanged = on; } void ResourceGroup::changed() { if (m_project && !m_blockChanged) { m_project->changed( this ); } } void ResourceGroup::setId(const QString& id) { //debugPlan<setProject( project ); } } bool ResourceGroup::isScheduled() const { foreach ( Resource *r, m_resources ) { if ( r->isScheduled() ) { return true; } } return false; } bool ResourceGroup::isBaselined( long id ) const { Q_UNUSED(id); foreach ( const Resource *r, m_resources ) { if ( r->isBaselined() ) { return true; } } return false; } void ResourceGroup::addResource(int index, Resource* resource, Risk*) { int i = index == -1 ? m_resources.count() : index; resource->setParentGroup( this ); resource->setProject( m_project ); m_resources.insert(i, resource ); } Resource *ResourceGroup::takeResource(Resource *resource) { Resource *r = 0; int i = m_resources.indexOf(resource); if (i != -1) { r = m_resources.takeAt(i); r->setParentGroup( 0 ); r->setProject( 0 ); } return r; } int ResourceGroup::indexOf( const Resource *resource ) const { return m_resources.indexOf( const_cast( resource ) ); //??? } Risk* ResourceGroup::getRisk(int) { return 0L; } void ResourceGroup::addRequiredResource(ResourceGroup*) { } ResourceGroup* ResourceGroup::getRequiredResource(int) { return 0L; } void ResourceGroup::deleteRequiredResource(int) { } bool ResourceGroup::load(KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; setId(element.attribute("id")); m_name = element.attribute("name"); setType(element.attribute("type")); m_shared = element.attribute("shared", "0").toInt(); KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "resource") { // Load the resource Resource *child = new Resource(); if (child->load(e, status)) { addResource( -1, child, 0 ); } else { // TODO: Complain about this delete child; } } } return true; } void ResourceGroup::save(QDomElement &element) const { //debugPlan; QDomElement me = element.ownerDocument().createElement("resource-group"); element.appendChild(me); me.setAttribute("id", m_id); me.setAttribute("name", m_name); me.setAttribute("type", typeToString()); me.setAttribute("shared", m_shared); foreach (Resource *r, m_resources) { r->save(me); } } void ResourceGroup::saveWorkPackageXML( QDomElement &element, const QList &lst ) const { QDomElement me = element.ownerDocument().createElement( "resource-group" ); element.appendChild( me ); me.setAttribute( "id", m_id ); me.setAttribute( "name", m_name ); foreach ( Resource *r, m_resources ) { if ( lst.contains( r ) ) { r->save( me ); } } } void ResourceGroup::initiateCalculation(Schedule &sch) { foreach (Resource *r, m_resources) { r->initiateCalculation(sch); } clearNodes(); } int ResourceGroup::units() const { int u = 0; foreach ( const Resource *r, m_resources) { u += r->units(); } return u; } ResourceGroup *ResourceGroup::findId(const QString &id) const { return m_project ? m_project->findResourceGroup(id) : 0; } bool ResourceGroup::removeId(const QString &id) { return m_project ? m_project->removeResourceGroupId(id): false; } void ResourceGroup::insertId(const QString &id) { //debugPlan; if (m_project) m_project->insertResourceGroupId(id, this); } Appointment ResourceGroup::appointmentIntervals() const { Appointment a; foreach (Resource *r, m_resources) { a += r->appointmentIntervals(); } return a; } DateTime ResourceGroup::startTime( long id ) const { DateTime dt; foreach ( Resource *r, m_resources ) { DateTime t = r->startTime( id ); if ( ! dt.isValid() || t < dt ) { dt = t; } } return dt; } DateTime ResourceGroup::endTime( long id ) const { DateTime dt; foreach ( Resource *r, m_resources ) { DateTime t = r->endTime( id ); if ( ! dt.isValid() || t > dt ) { dt = t; } } return dt; } bool ResourceGroup::isShared() const { return m_shared; } void ResourceGroup::setShared(bool on) { m_shared = on; } Resource::Resource() : QObject( 0 ), // atm QObject is only for casting m_project(0), m_parent( 0 ), m_autoAllocate( false ), m_currentSchedule( 0 ), m_blockChanged(false), m_shared(false) { m_type = Type_Work; m_units = 100; // % // m_availableFrom = DateTime( QDate::currentDate(), QTime( 0, 0, 0 ) ); // m_availableUntil = m_availableFrom.addYears(2); cost.normalRate = 100; cost.overtimeRate = 0; cost.fixed = 0; cost.account = 0; m_calendar = 0; m_currentSchedule = 0; //debugPlan<<"("<setState( CalendarDay::Working ); wd->addInterval( TimeInterval( QTime( 0, 0, 0 ), 24*60*60*1000 ) ); } } Resource::Resource(Resource *resource) : QObject( 0 ), // atm QObject is only for casting m_project( 0 ), m_parent( 0 ), m_currentSchedule( 0 ), m_shared(false) { //debugPlan<<"("<removeRunning(*this); } } void Resource::removeRequests() { foreach (ResourceRequest *r, m_requests) { r->setResource(0); // avoid the request to mess with my list r->parent()->deleteResourceRequest(r); } m_requests.clear(); } void Resource::setId(const QString& id) { //debugPlan<appointments(); // Note m_id = resource->id(); m_name = resource->name(); m_initials = resource->initials(); m_email = resource->email(); m_autoAllocate = resource->m_autoAllocate; m_availableFrom = resource->availableFrom(); m_availableUntil = resource->availableUntil(); m_units = resource->units(); // available units in percent m_type = resource->type(); cost.normalRate = resource->normalRate(); cost.overtimeRate = resource->overtimeRate(); cost.account = resource->account(); m_calendar = resource->m_calendar; m_requiredIds = resource->requiredIds(); m_teamMembers = resource->m_teamMembers; // hmmmm //m_externalAppointments = resource->m_externalAppointments; //m_externalNames = resource->m_externalNames; } void Resource::blockChanged(bool on) { m_blockChanged = on; } void Resource::changed() { if (m_project && !m_blockChanged) { m_project->changed( this ); } } void Resource::setType( Type type ) { m_type = type; changed(); } void Resource::setType(const QString &type) { if (type == "Work") setType( Type_Work ); else if (type == "Material") setType( Type_Material ); else if (type == "Team") setType( Type_Team ); else setType( Type_Work ); } QString Resource::typeToString( bool trans ) const { return typeToStringList( trans ).at( m_type ); } QStringList Resource::typeToStringList( bool trans ) { // keep these in the same order as the enum! return QStringList() << (trans ? xi18nc( "@item:inlistbox resource type", "Work" ) : QString( "Work") ) << (trans ? xi18nc( "@item:inlistbox resource type", "Material" ) : QString( "Material" ) ) << (trans ? xi18nc( "@item:inlistbox resource type", "Team" ) : QString( "Team" ) ); } void Resource::setName( const QString &n ) { m_name = n.trimmed(); changed(); } void Resource::setInitials( const QString &initials ) { m_initials = initials.trimmed(); changed(); } void Resource::setEmail( const QString &email ) { m_email = email; changed(); } bool Resource::autoAllocate() const { return m_autoAllocate; } void Resource::setAutoAllocate( bool on ) { if ( m_autoAllocate != on ) { m_autoAllocate = on; changed(); } } void Resource::setUnits( int units ) { m_units = units; m_workinfocache.clear(); changed(); } Calendar *Resource::calendar( bool local ) const { if ( local || m_calendar ) { return m_calendar; } // No calendar is set, try default calendar Calendar *c = 0; if ( m_type == Type_Work && project() ) { c = project()->defaultCalendar(); } else if ( m_type == Type_Material ) { c = const_cast( &m_materialCalendar ); } return c; } void Resource::setCalendar( Calendar *calendar ) { m_calendar = calendar; m_workinfocache.clear(); changed(); } DateTime Resource::firstAvailableAfter(const DateTime &, const DateTime & ) const { return DateTime(); } DateTime Resource::getBestAvailableTime(const Duration &/*duration*/) { return DateTime(); } DateTime Resource::getBestAvailableTime(const DateTime &/*after*/, const Duration &/*duration*/) { return DateTime(); } bool Resource::load(KoXmlElement &element, XMLLoaderObject &status) { //debugPlan; const Locale *locale = status.project().locale(); QString s; setId(element.attribute("id")); m_name = element.attribute("name"); m_initials = element.attribute("initials"); m_email = element.attribute("email"); m_autoAllocate = (bool)(element.attribute( "auto-allocate", "0" ).toInt()); setType(element.attribute("type")); m_shared = element.attribute("shared", "0").toInt(); m_calendar = status.project().findCalendar(element.attribute("calendar-id")); m_units = element.attribute("units", "100").toInt(); s = element.attribute("available-from"); if (!s.isEmpty()) m_availableFrom = DateTime::fromString(s, status.projectTimeZone()); s = element.attribute("available-until"); if (!s.isEmpty()) m_availableUntil = DateTime::fromString(s, status.projectTimeZone()); // NOTE: money was earlier (2.x) saved with symbol so we need to handle that QString money = element.attribute("normal-rate"); bool ok = false; cost.normalRate = money.toDouble(&ok); if (!ok) { cost.normalRate = locale->readMoney(money); debugPlan<<"normal-rate failed, tried readMoney()"<"<readMoney(money); debugPlan<<"overtime-rate failed, tried readMoney()"<"<setIntervals( lst ); a->setAuxcilliaryInfo( e.attribute( "name", "Unknown" ) ); m_externalAppointments[ id ] = a; } } loadCalendarIntervalsCache( element, status ); return true; } QList Resource::requiredResources() const { QList lst; foreach ( const QString &s, m_requiredIds ) { Resource *r = findId( s ); if ( r ) { lst << r; } } return lst; } void Resource::setRequiredIds( const QStringList &ids ) { debugPlan<removeRunning(*this); } cost.account = account; changed(); } void Resource::save(QDomElement &element) const { //debugPlan; QDomElement me = element.ownerDocument().createElement("resource"); element.appendChild(me); if (calendar(true)) me.setAttribute("calendar-id", m_calendar->id()); me.setAttribute("id", m_id); me.setAttribute("name", m_name); me.setAttribute("initials", m_initials); me.setAttribute("email", m_email); me.setAttribute("auto-allocate", m_autoAllocate ); me.setAttribute("type", typeToString()); me.setAttribute("shared", m_shared); me.setAttribute("units", QString::number(m_units)); if ( m_availableFrom.isValid() ) { me.setAttribute("available-from", m_availableFrom.toString( Qt::ISODate )); } if ( m_availableUntil.isValid() ) { me.setAttribute("available-until", m_availableUntil.toString( Qt::ISODate )); } QString money; me.setAttribute("normal-rate", money.setNum(cost.normalRate)); me.setAttribute("overtime-rate", money.setNum(cost.overtimeRate)); if ( cost.account ) { me.setAttribute("account", cost.account->name()); } if ( ! m_requiredIds.isEmpty() ) { QDomElement e = me.ownerDocument().createElement("required-resources"); me.appendChild(e); foreach ( const QString &id, m_requiredIds ) { QDomElement el = e.ownerDocument().createElement("resource"); e.appendChild( el ); el.setAttribute( "id", id ); } } if ( ! m_externalAppointments.isEmpty() ) { QDomElement e = me.ownerDocument().createElement("external-appointments"); me.appendChild(e); foreach ( const QString &id, m_externalAppointments.uniqueKeys() ) { QDomElement el = e.ownerDocument().createElement("project"); e.appendChild( el ); el.setAttribute( "id", id ); el.setAttribute( "name", m_externalAppointments[ id ]->auxcilliaryInfo() ); m_externalAppointments[ id ]->intervals().saveXML( el ); } } saveCalendarIntervalsCache( me ); } bool Resource::isAvailable(Task * /*task*/) { bool busy = false; /* foreach (Appointment *a, m_appointments) { if (a->isBusy(task->startTime(), task->endTime())) { busy = true; break; } }*/ return !busy; } QList Resource::appointments( long id ) const { Schedule *s = schedule( id ); if ( s == 0 ) { return QList(); } return s->appointments(); } bool Resource::addAppointment(Appointment *appointment) { if (m_currentSchedule) return m_currentSchedule->add(appointment); return false; } bool Resource::addAppointment(Appointment *appointment, Schedule &main) { Schedule *s = findSchedule(main.id()); if (s == 0) { s = createSchedule(&main); } appointment->setResource(s); return s->add(appointment); } // called from makeAppointment void Resource::addAppointment( Schedule *node, const DateTime &start, const DateTime &end, double load ) { Q_ASSERT( start < end ); Schedule *s = findSchedule(node->id()); if (s == 0) { s = createSchedule(node->parent()); } s->setCalculationMode( node->calculationMode() ); //debugPlan<<"id="<id()<<" Mode="<calculationMode()<<""<addAppointment(node, start, end, load); } void Resource::initiateCalculation(Schedule &sch) { m_currentSchedule = createSchedule(&sch); } Schedule *Resource::schedule( long id ) const { return id == -1 ? m_currentSchedule : findSchedule( id ); } bool Resource::isBaselined( long id ) const { if ( m_type == Resource::Type_Team ) { foreach ( const Resource *r, teamMembers() ) { if ( r->isBaselined( id ) ) { return true; } } return false; } Schedule *s = schedule( id ); return s ? s->isBaselined() : false; } Schedule *Resource::findSchedule( long id ) const { if ( m_schedules.contains( id ) ) { return m_schedules[ id ]; } if ( id == CURRENTSCHEDULE ) { return m_currentSchedule; } if ( id == BASELINESCHEDULE || id == ANYSCHEDULED ) { foreach ( Schedule *s, m_schedules ) { if ( s->isBaselined() ) { return s; } } } if ( id == ANYSCHEDULED ) { foreach ( Schedule *s, m_schedules ) { if ( s->isScheduled() ) { return s; } } } return 0; } bool Resource::isScheduled() const { foreach ( Schedule *s, m_schedules ) { if ( s->isScheduled() ) { return true; } } return false; } void Resource::deleteSchedule(Schedule *schedule) { takeSchedule(schedule); delete schedule; } void Resource::takeSchedule(const Schedule *schedule) { if (schedule == 0) return; if (m_currentSchedule == schedule) m_currentSchedule = 0; m_schedules.take(schedule->id()); } void Resource::addSchedule(Schedule *schedule) { if (schedule == 0) return; m_schedules.remove(schedule->id()); m_schedules.insert(schedule->id(), schedule); } ResourceSchedule *Resource::createSchedule(const QString& name, int type, long id) { ResourceSchedule *sch = new ResourceSchedule(this, name, (Schedule::Type)type, id); addSchedule(sch); return sch; } ResourceSchedule *Resource::createSchedule(Schedule *parent) { ResourceSchedule *sch = new ResourceSchedule(parent, this); //debugPlan<<"id="<id(); addSchedule(sch); return sch; } QTimeZone Resource::timeZone() const { Calendar *cal = calendar(); return cal ? cal->timeZone() : m_project ? m_project->timeZone() : /* else */ QTimeZone(); } DateTimeInterval Resource::requiredAvailable(Schedule *node, const DateTime &start, const DateTime &end ) const { Q_ASSERT( m_currentSchedule ); DateTimeInterval interval( start, end ); #ifndef PLAN_NLOGDEBUG if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available in interval: %1" ).arg( interval.toString() ) ); #endif DateTime availableFrom = m_availableFrom.isValid() ? m_availableFrom : ( m_project ? m_project->constraintStartTime() : DateTime() ); DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : ( m_project ? m_project->constraintEndTime() : DateTime() ); DateTimeInterval x = interval.limitedTo( availableFrom, availableUntil ); if ( calendar() == 0 ) { #ifndef PLAN_NLOGDEBUG if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available: no calendar, %1" ).arg( x.toString() ) ); #endif return x; } DateTimeInterval i = m_currentSchedule->firstBookedInterval( x, node ); if ( i.isValid() ) { #ifndef PLAN_NLOGDEBUG if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available: booked, %1" ).arg( i.toString() ) ); #endif return i; } i = calendar()->firstInterval(x.first, x.second, m_currentSchedule); #ifndef PLAN_NLOGDEBUG if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required first available in %1: %2" ).arg( x.toString() ).arg( i.toString() ) ); #endif return i; } void Resource::makeAppointment(Schedule *node, const DateTime &from, const DateTime &end, int load, const QList &required ) { //debugPlan<<"node id="<id()<<" mode="<calculationMode()<<""<logWarning( i18n( "Make appointments: Invalid time" ) ); return; } Calendar *cal = calendar(); if (cal == 0) { m_currentSchedule->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) ); return; } #ifndef PLAN_NLOGDEBUG if ( m_currentSchedule ) { QStringList lst; foreach ( Resource *r, required ) { lst << r->name(); } m_currentSchedule->logDebug( QString( "Make appointments from %1 to %2 load=%4, required: %3" ).arg( from.toString() ).arg( end.toString() ).arg( lst.join(",") ).arg( load ) ); } #endif AppointmentIntervalList lst = workIntervals( from, end, m_currentSchedule ); foreach ( const AppointmentInterval &i, lst.map() ) { m_currentSchedule->addAppointment( node, i.startTime(), i.endTime(), load ); foreach ( Resource *r, required ) { r->addAppointment( node, i.startTime(), i.endTime(), r->units() ); //FIXME: units may not be correct } } } void Resource::makeAppointment(Schedule *node, int load, const QList &required) { //debugPlan<startTime; QLocale locale; if (!node->startTime.isValid()) { m_currentSchedule->logWarning( i18n( "Make appointments: Node start time is not valid" ) ); return; } if (!node->endTime.isValid()) { m_currentSchedule->logWarning( i18n( "Make appointments: Node end time is not valid" ) ); return; } if ( m_type == Type_Team ) { #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug( "Make appointments to team " + m_name ); #endif Duration e; foreach ( Resource *r, teamMembers() ) { r->makeAppointment( node, load, required ); } return; } node->resourceNotAvailable = false; node->workStartTime = DateTime(); node->workEndTime = DateTime(); Calendar *cal = calendar(); if (m_type == Type_Material) { DateTime from = availableAfter(node->startTime, node->endTime); DateTime end = availableBefore(node->endTime, node->startTime); if (!from.isValid() || !end.isValid()) { return; } if (cal == 0) { // Allocate the whole period addAppointment(node, from, end, m_units); return; } makeAppointment(node, from, end, load); return; } if (!cal) { m_currentSchedule->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) ); return; } DateTime time = node->startTime; DateTime end = node->endTime; time = availableAfter(time, end); if (!time.isValid()) { m_currentSchedule->logWarning( i18n( "Resource %1 not available in interval: %2 to %3", m_name, locale.toString(node->startTime, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat) ) ); node->resourceNotAvailable = true; return; } end = availableBefore(end, time); foreach ( Resource *r, required ) { time = r->availableAfter( time, end ); end = r->availableBefore( end, time ); if ( ! ( time.isValid() && end.isValid() ) ) { #ifndef PLAN_NLOGDEBUG if ( m_currentSchedule ) m_currentSchedule->logDebug( "The required resource '" + r->name() + "'is not available in interval:" + node->startTime.toString() + ',' + node->endTime.toString() ); #endif break; } } if (!end.isValid()) { m_currentSchedule->logWarning( i18n( "Resource %1 not available in interval: %2 to %3", m_name, locale.toString(time, QLocale::ShortFormat), locale.toString(node->endTime, QLocale::ShortFormat) ) ); node->resourceNotAvailable = true; return; } //debugPlan<allowOverbooking() ) { foreach ( const Appointment *a, sch->appointments( sch->calculationMode() ) ) { work -= a->intervals(); } foreach ( const Appointment *a, m_externalAppointments ) { work -= a->intervals(); } } return work; } void Resource::calendarIntervals( const DateTime &from, const DateTime &until ) const { Calendar *cal = calendar(); if ( cal == 0 ) { m_workinfocache.clear(); return; } if ( cal->cacheVersion() != m_workinfocache.version ) { m_workinfocache.clear(); m_workinfocache.version = cal->cacheVersion(); } if ( ! m_workinfocache.isValid() ) { // First time // debugPlan<<"First time:"<workIntervals( from, until, m_units ); // debugPlan<<"calendarIntervals (first):"<workIntervals( from, m_workinfocache.start, m_units ); m_workinfocache.start = from; debugPlan<<"calendarIntervals (start):"< m_workinfocache.end ) { // debugPlan<<"Add to end:"<workIntervals( m_workinfocache.end, until, m_units ); m_workinfocache.end = until; debugPlan<<"calendarIntervals: (end)"<::const_iterator it = intervals.map().constEnd(); if ( start.isValid() && start <= time ) { // possibly useful cache it = intervals.map().lowerBound( time.date() ); } if ( it == intervals.map().constEnd() ) { // nothing cached, check the old way DateTime t = cal ? cal->firstAvailableAfter( time, limit, sch ) : DateTime(); return t; } AppointmentInterval inp( time, limit ); for ( ; it != intervals.map().constEnd() && it.key() <= limit.date(); ++it ) { if ( ! it.value().intersects( inp ) && it.value() < inp ) { continue; } if ( sch ) { DateTimeInterval ti = sch->available( DateTimeInterval( it.value().startTime(), it.value().endTime() ) ); if ( ti.isValid() && ti.first < limit ) { ti.first = qMax( ti.first, time ); return ti.first; } } else { DateTime t = qMax( it.value().startTime(), time ); return t; } } if ( it == intervals.map().constEnd() ) { // ran out of cache, check the old way DateTime t = cal ? cal->firstAvailableAfter( time, limit, sch ) : DateTime(); return t; } return DateTime(); } DateTime Resource::WorkInfoCache::firstAvailableBefore( const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch ) const { if ( time <= limit ) { return DateTime(); } QMultiMap::const_iterator it = intervals.map().constBegin(); if ( time.isValid() && limit.isValid() && end.isValid() && end >= time && ! intervals.isEmpty() ) { // possibly useful cache it = intervals.map().upperBound( time.date() ); } if ( it == intervals.map().constBegin() ) { // nothing cached, check the old way DateTime t = cal ? cal->firstAvailableBefore( time, limit, sch ) : DateTime(); return t; } AppointmentInterval inp( limit, time ); for ( --it; it != intervals.map().constBegin() && it.key() >= limit.date(); --it ) { if ( ! it.value().intersects( inp ) && inp < it.value() ) { continue; } if ( sch ) { DateTimeInterval ti = sch->available( DateTimeInterval( it.value().startTime(), it.value().endTime() ) ); if ( ti.isValid() && ti.second > limit ) { ti.second = qMin( ti.second, time ); return ti.second; } } else { DateTime t = qMin( it.value().endTime(), time ); return t; } } if ( it == intervals.map().constBegin() ) { // ran out of cache, check the old way DateTime t = cal ? cal->firstAvailableBefore( time, limit, sch ) : DateTime(); return t; } return DateTime(); } bool Resource::WorkInfoCache::load( const KoXmlElement &element, XMLLoaderObject &status ) { clear(); version = element.attribute( "version" ).toInt(); effort = Duration::fromString( element.attribute( "effort" ) ); start = DateTime::fromString( element.attribute( "start" ) ); end = DateTime::fromString( element.attribute( "end" ) ); KoXmlElement e = element.namedItem( "intervals" ).toElement(); if ( ! e.isNull() ) { intervals.loadXML( e, status ); } //debugPlan<<*this; return true; } void Resource::WorkInfoCache::save( QDomElement &element ) const { element.setAttribute( "version", QString::number(version) ); element.setAttribute( "effort", effort.toString() ); element.setAttribute( "start", start.toString( Qt::ISODate ) ); element.setAttribute( "end", end.toString( Qt::ISODate ) ); QDomElement me = element.ownerDocument().createElement("intervals"); element.appendChild(me); intervals.saveXML( me ); } Duration Resource::effort( const DateTime& start, const Duration& duration, int units, bool backward, const QList< Resource* >& required ) const { return effort( m_currentSchedule, start, duration, units, backward, required ); } // the amount of effort we can do within the duration Duration Resource::effort( Schedule *sch, const DateTime &start, const Duration &duration, int units, bool backward, const QList &required ) const { //debugPlan<logDebug( QString( "Check effort in interval %1: %2, %3").arg(backward?"backward":"forward").arg( start.toString() ).arg( (backward?start-duration:start+duration).toString() ) ); #endif Duration e; if ( duration == 0 || m_units == 0 || units == 0 ) { warnPlan<<"zero duration or zero units"; return e; } if ( m_type == Type_Team ) { errorPlan<<"A team resource cannot deliver any effort"; return e; } Calendar *cal = calendar(); if ( cal == 0 ) { if ( sch ) sch->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) ); return e; } DateTime from; DateTime until; if ( backward ) { from = availableAfter( start - duration, start, sch ); until = availableBefore( start, start - duration, sch ); } else { from = availableAfter( start, start + duration, sch ); until = availableBefore( start + duration, start, sch ); } if ( ! ( from.isValid() && until.isValid() ) ) { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "Resource not available in interval:" + start.toString() + ',' + (start+duration).toString() ); #endif } else { foreach ( Resource *r, required ) { from = r->availableAfter( from, until ); until = r->availableBefore( until, from ); if ( ! ( from.isValid() && until.isValid() ) ) { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "The required resource '" + r->name() + "'is not available in interval:" + start.toString() + ',' + (start+duration).toString() ); #endif break; } } } if ( from.isValid() && until.isValid() ) { #ifndef PLAN_NLOGDEBUG if ( sch && until < from ) sch->logDebug( " until < from: until=" + until.toString() + " from=" + from.toString() ); #endif e = workIntervals( from, until ).effort( from, until ) * units / 100; if ( sch && ( ! sch->allowOverbooking() || sch->allowOverbookingState() == Schedule::OBS_Deny ) ) { Duration avail = workIntervals( from, until, sch ).effort( from, until ); if ( avail < e ) { e = avail; } } // e = ( cal->effort( from, until, sch ) ) * m_units / 100; } //debugPlan<logDebug( QString( "effort: %1 for %2 hours = %3" ).arg( start.toString() ).arg( duration.toString( Duration::Format_HourFraction ) ).arg( e.toString( Duration::Format_HourFraction ) ) ); #endif return e; } DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit ) const { return availableAfter( time, limit, m_currentSchedule ); } DateTime Resource::availableBefore(const DateTime &time, const DateTime &limit) const { return availableBefore( time, limit, m_currentSchedule ); } DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit, Schedule *sch) const { debugPlan<constraintEndTime() : DateTime() ); if (limit.isValid() && limit < lmt) { lmt = limit; } if (time >= lmt) { debugPlan<= limit"<logWarning( i18n( "Resource %1 has no calendar defined", m_name ) ); debugPlan<constraintStartTime() : DateTime() ); t = availableFrom > time ? availableFrom : time; if ( t >= lmt ) { debugPlan<timeZone(); t = t.toTimeZone( tz ); lmt = lmt.toTimeZone( tz ); t = m_workinfocache.firstAvailableAfter( t, lmt, cal, sch ); // t = cal->firstAvailableAfter(t, lmt, sch); //debugPlan<constraintStartTime() : DateTime() ); if (limit.isValid() && limit > lmt) { lmt = limit; } if (time <= lmt) { return t; } Calendar *cal = calendar(); if (cal == 0) { return t; } DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : ( m_project ? m_project->constraintEndTime() : DateTime() ); if ( ! availableUntil.isValid() ) { #ifndef PLAN_NLOGDEBUG if ( sch ) sch->logDebug( "availableUntil is invalid" ); #endif t = time; } else { t = availableUntil < time ? availableUntil : time; } #ifndef PLAN_NLOGDEBUG if ( sch && t < lmt ) sch->logDebug( "t < lmt: " + t.toString() + " < " + lmt.toString() ); #endif QTimeZone tz = cal->timeZone(); t = t.toTimeZone( tz ); lmt = lmt.toTimeZone( tz ); t = m_workinfocache.firstAvailableBefore( t, lmt, cal, sch ); // t = cal->firstAvailableBefore(t, lmt, sch ); #ifndef PLAN_NLOGDEBUG if ( sch && t.isValid() && t < lmt ) sch->logDebug( " t < lmt: t=" + t.toString() + " lmt=" + lmt.toString() ); #endif return t; } Resource *Resource::findId(const QString &id) const { return m_project ? m_project->findResource(id) : 0; } bool Resource::removeId(const QString &id) { return m_project ? m_project->removeResourceId(id) : false; } void Resource::insertId(const QString &id) { //debugPlan; if (m_project) m_project->insertResourceId(id, this); } Calendar *Resource::findCalendar(const QString &id) const { return (m_project ? m_project->findCalendar(id) : 0); } bool Resource::isOverbooked() const { return isOverbooked( DateTime(), DateTime() ); } bool Resource::isOverbooked(const QDate &date) const { return isOverbooked( DateTime( date ), DateTime( date.addDays(1 ) ) ); } bool Resource::isOverbooked(const DateTime &start, const DateTime &end) const { //debugPlan<isOverbooked(start, end) : false; } Appointment Resource::appointmentIntervals( long id ) const { Appointment a; Schedule *s = findSchedule( id ); if ( s == 0 ) { return a; } foreach (Appointment *app, static_cast( s )->appointments()) { a += *app; } return a; } Appointment Resource::appointmentIntervals() const { Appointment a; if (m_currentSchedule == 0) return a; foreach (Appointment *app, m_currentSchedule->appointments()) { a += *app; } return a; } EffortCostMap Resource::plannedEffortCostPrDay( const QDate &start, const QDate &end, long id, EffortCostCalculationType typ ) { EffortCostMap ec; Schedule *s = findSchedule( id ); if ( s == 0 ) { return ec; } ec = s->plannedEffortCostPrDay( start, end, typ ); return ec; } Duration Resource::plannedEffort( const QDate &date, EffortCostCalculationType typ ) const { return m_currentSchedule ? m_currentSchedule->plannedEffort( date, typ ) : Duration::zeroDuration; } void Resource::setProject( Project *project ) { if ( project != m_project ) { if ( m_project ) { removeId(); } } m_project = project; } void Resource::addExternalAppointment(const QString& id, Appointment* a) { int row = -1; if ( m_externalAppointments.contains( id ) ) { int row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern emit externalAppointmentToBeRemoved( this, row ); delete m_externalAppointments.take( id ); emit externalAppointmentRemoved(); } if ( row == -1 ) { m_externalAppointments[ id ] = a; row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern m_externalAppointments.remove( id ); } emit externalAppointmentToBeAdded( this, row ); m_externalAppointments[ id ] = a; emit externalAppointmentAdded( this, a ); } void Resource::addExternalAppointment( const QString &id, const QString &name, const DateTime &from, const DateTime &end, double load ) { Appointment *a = m_externalAppointments.value( id ); if ( a == 0 ) { a = new Appointment(); a->setAuxcilliaryInfo( name ); a->addInterval( from, end, load ); //debugPlan<addInterval( from, end, load ); emit externalAppointmentChanged( this, a ); } } void Resource::subtractExternalAppointment( const QString &id, const DateTime &start, const DateTime &end, double load ) { Appointment *a = m_externalAppointments.value( id ); if ( a ) { //debugPlan<intervals(); } AppointmentIntervalList Resource::externalAppointments( const DateTimeInterval &interval ) const { //debugPlan<extractIntervals( interval ) : *a; } return app.intervals(); } QMap Resource::externalProjects() const { QMap map; for ( QMapIterator it( m_externalAppointments ); it.hasNext(); ) { it.next(); if ( ! map.contains( it.key() ) ) { map[ it.key() ] = it.value()->auxcilliaryInfo(); } } // debugPlan<effort( time, duration, 100, backward ); } } else { e = effort( time, duration, 100, backward ); } return e.minutes(); } DateTime Resource::startTime( long id ) const { DateTime dt; Schedule *s = schedule( id ); if ( s == 0 ) { return dt; } foreach ( Appointment *a, s->appointments() ) { DateTime t = a->startTime(); if ( ! dt.isValid() || t < dt ) { dt = t; } } return dt; } DateTime Resource::endTime( long id ) const { DateTime dt; Schedule *s = schedule( id ); if ( s == 0 ) { return dt; } foreach ( Appointment *a, s->appointments() ) { DateTime t = a->endTime(); if ( ! dt.isValid() || t > dt ) { dt = t; } } return dt; } QList Resource::teamMembers() const { QList lst; foreach ( const QString &s, m_teamMembers ) { Resource *r = findId( s ); if ( r ) { lst << r; } } return lst; } QStringList Resource::teamMemberIds() const { return m_teamMembers; } void Resource::addTeamMemberId( const QString &id ) { if ( ! id.isEmpty() && ! m_teamMembers.contains( id ) ) { m_teamMembers.append( id ); } } void Resource::removeTeamMemberId( const QString &id ) { if ( m_teamMembers.contains( id ) ) { m_teamMembers.removeAt( m_teamMembers.indexOf( id ) ); } } void Resource::setTeamMemberIds(const QStringList &ids) { m_teamMembers = ids; } bool Resource::isShared() const { return m_shared; } void Resource::setShared(bool on) { m_shared = on; } QDebug operator<<( QDebug dbg, const KPlato::Resource::WorkInfoCache &c ) { dbg.nospace()<<"WorkInfoCache: ["<<" version="<requiredResources(); } //debugPlan<<"("<name() : QString("None")); } ResourceRequest::ResourceRequest(const ResourceRequest &r) : m_resource(r.m_resource), m_units(r.m_units), m_parent(0), m_dynamic(r.m_dynamic), m_required(r.m_required) { } ResourceRequest::~ResourceRequest() { //debugPlan<<"("<name() : QString("None")); if (m_resource) m_resource->unregisterRequest(this); m_resource = 0; qDeleteAll( m_teamMembers ); } bool ResourceRequest::load(KoXmlElement &element, Project &project) { //debugPlan; m_resource = project.resource(element.attribute("resource-id")); if (m_resource == 0) { warnPlan<<"The referenced resource does not exist: resource id="<id()); me.setAttribute("units", QString::number(m_units)); if ( ! m_required.isEmpty() ) { QDomElement e = me.ownerDocument().createElement("required-resources"); me.appendChild(e); foreach ( Resource *r, m_required ) { QDomElement el = e.ownerDocument().createElement("resource"); e.appendChild( el ); el.setAttribute( "id", r->id() ); } } } int ResourceRequest::units() const { //debugPlan<name()<<": units="<task() : 0; } void ResourceRequest::changed() { if ( task() ) { task()->changed(); } } void ResourceRequest::setCurrentSchedulePtr( Schedule *ns ) { setCurrentSchedulePtr( m_resource, ns ); } void ResourceRequest::setCurrentSchedulePtr( Resource *resource, Schedule *ns ) { resource->setCurrentSchedulePtr( resourceSchedule( ns, resource ) ); if( resource->type() == Resource::Type_Team ) { foreach ( Resource *member, resource->teamMembers() ) { member->setCurrentSchedulePtr( resourceSchedule( ns, member ) ); } } foreach ( Resource *r, m_required ) { r->setCurrentSchedulePtr( resourceSchedule( ns, r ) ); } } Schedule *ResourceRequest::resourceSchedule( Schedule *ns, Resource *res ) { if ( ns == 0 ) { return 0; } Resource *r = res == 0 ? resource() : res; Schedule *s = r->findSchedule(ns->id()); if (s == 0) { s = r->createSchedule(ns->parent()); } s->setCalculationMode( ns->calculationMode() ); s->setAllowOverbookingState( ns->allowOverbookingState() ); static_cast( s )->setNodeSchedule( ns ); //debugPlan<name()<<": id="<id()<<" mode="<calculationMode(); return s; } DateTime ResourceRequest::workTimeAfter(const DateTime &dt, Schedule *ns) { if ( m_resource->type() == Resource::Type_Work ) { DateTime t = availableAfter( dt, ns ); foreach ( Resource *r, m_required ) { if ( ! t.isValid() ) { break; } t = r->availableAfter( t, DateTime(), resourceSchedule( ns, r ) ); } return t; } else if ( m_resource->type() == Resource::Type_Team ) { return availableAfter( dt, ns ); } return DateTime(); } DateTime ResourceRequest::workTimeBefore(const DateTime &dt, Schedule *ns) { if ( m_resource->type() == Resource::Type_Work ) { DateTime t = availableBefore( dt, ns ); foreach ( Resource *r, m_required ) { if ( ! t.isValid() ) { break; } t = r->availableBefore( t, DateTime(), resourceSchedule( ns, r ) ); } return t; } else if ( m_resource->type() == Resource::Type_Team ) { return availableBefore( dt, ns ); } return DateTime(); } DateTime ResourceRequest::availableFrom() { DateTime dt = m_resource->availableFrom(); if ( ! dt.isValid() ) { dt = m_resource->project()->constraintStartTime(); } return dt; } DateTime ResourceRequest::availableUntil() { DateTime dt = m_resource->availableUntil(); if ( ! dt.isValid() ) { dt = m_resource->project()->constraintEndTime(); } return dt; } DateTime ResourceRequest::availableAfter(const DateTime &time, Schedule *ns) { if ( m_resource->type() == Resource::Type_Team ) { DateTime t;// = m_resource->availableFrom(); foreach ( Resource *r, m_resource->teamMembers() ) { setCurrentSchedulePtr( r, ns ); DateTime x = r->availableAfter( time ); if ( x.isValid() ) { t = t.isValid() ? qMin( t, x ) : x; } } return t; } setCurrentSchedulePtr( ns ); return m_resource->availableAfter( time ); } DateTime ResourceRequest::availableBefore(const DateTime &time, Schedule *ns) { if ( m_resource->type() == Resource::Type_Team ) { DateTime t; foreach ( Resource *r, m_resource->teamMembers() ) { setCurrentSchedulePtr( r, ns ); DateTime x = r->availableBefore( time ); if ( x.isValid() ) { t = t.isValid() ? qMax( t, x ) : x; } } return t; } setCurrentSchedulePtr( ns ); return resource()->availableBefore( time ); } Duration ResourceRequest::effort( const DateTime &time, const Duration &duration, Schedule *ns, bool backward ) { setCurrentSchedulePtr( ns ); Duration e = m_resource->effort( time, duration, m_units, backward, m_required ); //debugPlan<name()<makeAppointment( ns, ( m_resource->units() * m_units / 100 ), m_required ); } } void ResourceRequest::makeAppointment( Schedule *ns, int amount ) { if ( m_resource ) { setCurrentSchedulePtr( ns ); m_resource->makeAppointment( ns, amount, m_required ); } } long ResourceRequest::allocationSuitability( const DateTime &time, const Duration &duration, Schedule *ns, bool backward ) { setCurrentSchedulePtr( ns ); return resource()->allocationSuitability( time, duration, backward ); } QList ResourceRequest::teamMembers() const { qDeleteAll( m_teamMembers ); m_teamMembers.clear(); if ( m_resource->type() == Resource::Type_Team ) { foreach ( Resource *r, m_resource->teamMembers() ) { m_teamMembers << new ResourceRequest( r, m_units ); } } return m_teamMembers; } QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest *rr ) { if (rr) { dbg<<*rr; } else { dbg<<(void*)rr; } return dbg; } QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest &rr ) { if (rr.resource()) { dbg<<"ResourceRequest["<name()<<']'; } else { dbg<<"ResourceRequest[No resource]"; } return dbg; } ///////// ResourceGroupRequest::ResourceGroupRequest(ResourceGroup *group, int units) : m_group(group), m_units(units), m_parent(0) { //debugPlan<<"Request to:"<<(group ? group->name() : QString("None")); if (group) group->registerRequest(this); } ResourceGroupRequest::ResourceGroupRequest(const ResourceGroupRequest &g) : m_group(g.m_group), m_units(g.m_units), m_parent(0) { } ResourceGroupRequest::~ResourceGroupRequest() { //debugPlan; if (m_group) m_group->unregisterRequest(this); while (!m_resourceRequests.isEmpty()) { delete m_resourceRequests.takeFirst(); } } void ResourceGroupRequest::addResourceRequest(ResourceRequest *request) { //debugPlan<<"("<setParent(this); m_resourceRequests.append(request); request->registerRequest(); changed(); } ResourceRequest *ResourceGroupRequest::takeResourceRequest(ResourceRequest *request) { if (request) request->unregisterRequest(); ResourceRequest *r = 0; int i = m_resourceRequests.indexOf(request); if (i != -1) { r = m_resourceRequests.takeAt(i); } changed(); return r; } ResourceRequest *ResourceGroupRequest::find(const Resource *resource) const { foreach (ResourceRequest *gr, m_resourceRequests) { if (gr->resource() == resource) { return gr; } } return 0; } ResourceRequest *ResourceGroupRequest::resourceRequest( const QString &name ) { foreach (ResourceRequest *r, m_resourceRequests) { if (r->resource()->name() == name ) return r; } return 0; } QStringList ResourceGroupRequest::requestNameList( bool includeGroup ) const { QStringList lst; if ( includeGroup && m_units > 0 && m_group ) { lst << m_group->name(); } foreach ( ResourceRequest *r, m_resourceRequests ) { if ( ! r->isDynamicallyAllocated() ) { Q_ASSERT( r->resource() ); lst << r->resource()->name(); } } return lst; } QList ResourceGroupRequest::requestedResources() const { QList lst; foreach ( ResourceRequest *r, m_resourceRequests ) { if ( ! r->isDynamicallyAllocated() ) { Q_ASSERT( r->resource() ); lst << r->resource(); } } return lst; } QList ResourceGroupRequest::resourceRequests( bool resolveTeam ) const { QList lst; foreach ( ResourceRequest *rr, m_resourceRequests ) { if ( resolveTeam && rr->resource()->type() == Resource::Type_Team ) { lst += rr->teamMembers(); } else { lst << rr; } } return lst; } bool ResourceGroupRequest::load(KoXmlElement &element, XMLLoaderObject &status) { //debugPlan; m_group = status.project().findResourceGroup(element.attribute("group-id")); if (m_group == 0) { errorPlan<<"The referenced resource group does not exist: group id="<registerRequest(this); KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "resource-request") { ResourceRequest *r = new ResourceRequest(); if (r->load(e, status.project())) addResourceRequest(r); else { errorPlan<<"Failed to load resource request"; delete r; } } } // meaning of m_units changed // Pre 0.6.6 the number *included* all requests, now it is in *addition* to resource requests m_units = element.attribute("units").toInt(); if ( status.version() < "0.6.6" ) { int x = m_units - m_resourceRequests.count(); m_units = x > 0 ? x : 0; } return true; } void ResourceGroupRequest::save(QDomElement &element) const { QDomElement me = element.ownerDocument().createElement("resourcegroup-request"); element.appendChild(me); me.setAttribute("group-id", m_group->id()); me.setAttribute("units", QString::number(m_units)); foreach (ResourceRequest *r, m_resourceRequests) r->save(me); } int ResourceGroupRequest::units() const { return m_units; } Duration ResourceGroupRequest::duration(const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) { Duration dur; if ( m_parent ) { dur = m_parent->duration( m_resourceRequests, time, _effort, ns, backward ); } return dur; } DateTime ResourceGroupRequest::workTimeAfter(const DateTime &time, Schedule *ns) { DateTime start; if ( m_resourceRequests.isEmpty() ) { return start; } foreach (ResourceRequest *r, m_resourceRequests) { DateTime t = r->workTimeAfter( time, ns ); if (t.isValid() && (!start.isValid() || t < start)) start = t; } if (start.isValid() && start < time) start = time; //debugPlan<workTimeBefore( time, ns ); if (t.isValid() && (!end.isValid() ||t > end)) end = t; } if (!end.isValid() || end > time) end = time; return end; } DateTime ResourceGroupRequest::availableAfter(const DateTime &time, Schedule *ns) { DateTime start; if ( m_resourceRequests.isEmpty() ) { return start; } foreach (ResourceRequest *r, m_resourceRequests) { DateTime t = r->availableAfter(time, ns); if (t.isValid() && (!start.isValid() || t < start)) start = t; } if (start.isValid() && start < time) start = time; //debugPlan<name(); return start; } DateTime ResourceGroupRequest::availableBefore(const DateTime &time, Schedule *ns) { DateTime end; if ( m_resourceRequests.isEmpty() ) { return end; } foreach (ResourceRequest *r, m_resourceRequests) { DateTime t = r->availableBefore(time, ns); if (t.isValid() && (!end.isValid() || t > end)) end = t; } if (!end.isValid() || end > time) end = time; //debugPlan<name(); return end; } void ResourceGroupRequest::makeAppointments(Schedule *schedule) { //debugPlan; foreach (ResourceRequest *r, m_resourceRequests) { r->makeAppointment(schedule); } } void ResourceGroupRequest::reserve(const DateTime &start, const Duration &duration) { m_start = start; m_duration = duration; } bool ResourceGroupRequest::isEmpty() const { return m_resourceRequests.isEmpty() && m_units == 0; } Task *ResourceGroupRequest::task() const { return m_parent ? m_parent->task() : 0; } void ResourceGroupRequest::changed() { if ( m_parent ) m_parent->changed(); } void ResourceGroupRequest::deleteResourceRequest( ResourceRequest *request ) { int i = m_resourceRequests.indexOf( request ); if ( i != -1 ) { m_resourceRequests.removeAt( i ); } delete request; changed(); } void ResourceGroupRequest::resetDynamicAllocations() { QList lst; foreach ( ResourceRequest *r, m_resourceRequests ) { if ( r->isDynamicallyAllocated() ) { lst << r; } } while ( ! lst.isEmpty() ) { deleteResourceRequest( lst.takeFirst() ); } } void ResourceGroupRequest::allocateDynamicRequests( const DateTime &time, const Duration &effort, Schedule *ns, bool backward ) { int num = m_units; if ( num <= 0 ) { return; } if ( num == m_group->numResources() ) { // TODO: allocate all } Duration e = effort / m_units; QMap map; foreach ( Resource *r, m_group->resources() ) { if ( r->type() == Resource::Type_Team ) { continue; } ResourceRequest *rr = find( r ); if ( rr ) { if ( rr->isDynamicallyAllocated() ) { --num; // already allocated } continue; } rr = new ResourceRequest( r, r->units() ); long s = rr->allocationSuitability( time, e, ns, backward ); if ( s == 0 ) { // not suitable at all delete rr; } else { map.insertMulti( s, rr ); } } for ( --num; num >= 0 && ! map.isEmpty(); --num ) { long key = map.lastKey(); ResourceRequest *r = map.take( key ); r->setAllocatedDynaically( true ); addResourceRequest( r ); debugPlan<group() == request->group() ) { errorPlan<<"Request to this group already exists"; errorPlan<<"Task:"<name()<<"Group:"<group()->name(); Q_ASSERT( false ); } } m_requests.append( request ); request->setParent( this ); changed(); } ResourceGroupRequest *ResourceRequestCollection::find(const ResourceGroup *group) const { foreach (ResourceGroupRequest *r, m_requests) { if (r->group() == group) return r; // we assume only one request to the same group } return 0; } ResourceRequest *ResourceRequestCollection::find(const Resource *resource) const { ResourceRequest *req = 0; QListIterator it(m_requests); while (req == 0 && it.hasNext()) { req = it.next()->find(resource); } return req; } ResourceRequest *ResourceRequestCollection::resourceRequest( const QString &name ) const { ResourceRequest *req = 0; QListIterator it(m_requests); while (req == 0 && it.hasNext()) { req = it.next()->resourceRequest( name ); } return req; } QStringList ResourceRequestCollection::requestNameList( bool includeGroup ) const { QStringList lst; foreach ( ResourceGroupRequest *r, m_requests ) { lst << r->requestNameList( includeGroup ); } return lst; } QList ResourceRequestCollection::requestedResources() const { QList lst; foreach ( ResourceGroupRequest *g, m_requests ) { lst += g->requestedResources(); } return lst; } QList ResourceRequestCollection::resourceRequests( bool resolveTeam ) const { QList lst; foreach ( ResourceGroupRequest *g, m_requests ) { foreach ( ResourceRequest *r, g->resourceRequests( resolveTeam ) ) { lst << r; } } return lst; } bool ResourceRequestCollection::contains( const QString &identity ) const { QStringList lst = requestNameList(); return lst.indexOf( QRegExp( identity, Qt::CaseSensitive, QRegExp::FixedString ) ) != -1; } ResourceGroupRequest *ResourceRequestCollection::findGroupRequestById( const QString &id ) const { foreach ( ResourceGroupRequest *r, m_requests ) { if ( r->group()->id() == id ) { return r; } } return 0; } // bool ResourceRequestCollection::load(KoXmlElement &element, Project &project) { // //debugPlan; // return true; // } void ResourceRequestCollection::save(QDomElement &element) const { //debugPlan; foreach (ResourceGroupRequest *r, m_requests) { r->save(element); } } // Returns the duration needed by the working resources // "Material type" of resourcegroups does not (atm) affect the duration. Duration ResourceRequestCollection::duration(const DateTime &time, const Duration &effort, Schedule *ns, bool backward) { //debugPlan<<"time="<workTimeBefore( time, ns ); if (t.isValid() && (!end.isValid() ||t > end)) end = t; } if (!end.isValid() || end > time) end = time; return end; } DateTime ResourceRequestCollection::availableAfter(const DateTime &time, Schedule *ns) { DateTime start; foreach (ResourceGroupRequest *r, m_requests) { DateTime t = r->availableAfter(time, ns); if (t.isValid() && (!start.isValid() || t < start)) start = t; } if (start.isValid() && start < time) start = time; //debugPlan<availableBefore(time, ns); if (t.isValid() && (!end.isValid() ||t > end)) end = t; } if (!end.isValid() || end > time) end = time; return end; } DateTime ResourceRequestCollection::workStartAfter(const DateTime &time, Schedule *ns) { DateTime start; foreach (ResourceGroupRequest *r, m_requests) { if ( r->group()->type() != ResourceGroup::Type_Work ) { continue; } DateTime t = r->availableAfter(time, ns); if (t.isValid() && (!start.isValid() || t < start)) start = t; } if (start.isValid() && start < time) start = time; //debugPlan<group()->type() != ResourceGroup::Type_Work ) { continue; } DateTime t = r->availableBefore(time, ns); if (t.isValid() && (!end.isValid() ||t > end)) end = t; } if (!end.isValid() || end > time) end = time; return end; } void ResourceRequestCollection::makeAppointments(Schedule *schedule) { //debugPlan; foreach (ResourceGroupRequest *r, m_requests) { r->makeAppointments(schedule); } } void ResourceRequestCollection::reserve(const DateTime &start, const Duration &duration) { //debugPlan; foreach (ResourceGroupRequest *r, m_requests) { r->reserve(start, duration); } } bool ResourceRequestCollection::isEmpty() const { foreach (ResourceGroupRequest *r, m_requests) { if (!r->isEmpty()) return false; } return true; } void ResourceRequestCollection::changed() { //debugPlan<changed(); } } void ResourceRequestCollection::resetDynamicAllocations() { foreach ( ResourceGroupRequest *g, m_requests ) { g->resetDynamicAllocations(); } } Duration ResourceRequestCollection::effort( const QList &lst, const DateTime &time, const Duration &duration, Schedule *ns, bool backward ) const { Duration e; foreach (ResourceRequest *r, lst) { e += r->effort( time, duration, ns, backward ); //debugPlan<<(backward?"(B)":"(F)" )<availableUntil(); if (!t2.isValid() || t2 < t1) t2 = t1; } //debugPlan<<"fw"< &lst, const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) { //debugPlan<<"--->"<<(backward?"(B)":"(F)")<resource()->name(); } ns->logDebug( "Match effort:" + time.toString() + "," + _effort.toString() ); ns->logDebug( "Resources: " + ( nl.isEmpty() ? QString( "None" ) : nl.join( ", " ) ) ); } #endif QLocale locale; Duration e; if (_effort == Duration::zeroDuration) { return e; } DateTime logtime = time; bool match = false; DateTime start = time; int inc = backward ? -1 : 1; DateTime end = start; Duration e1; int nDays = numDays(lst, time, backward) + 1; int day = 0; for (day=0; !match && day <= nDays; ++day) { // days end = end.addDays(inc); e1 = effort( lst, start, backward ? start - end : end - start, ns, backward ); //debugPlan<<"["<logDebug( "Days: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' ); #endif logtime = start; for (int i=0; !match && i < 24; ++i) { // hours end = end.addSecs(inc*60*60); e1 = effort( lst, start, backward ? start - end : end - start, ns, backward ); if (e + e1 < _effort) { e += e1; start = end; } else if (e + e1 == _effort) { e += e1; match = true; } else { if ( false/*roundToHour*/ && ( _effort - e ) < ( e + e1 - _effort ) ) { end = start; match = true; } else { end = start; } break; } //debugPlan<<"duration(h)["< _effort) { end = start; break; } //debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<" time="<logDebug( "Minutes: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' ); #endif logtime = start; for (int i=0; !match && i < 60; ++i) { //seconds end = end.addSecs(inc); e1 = effort( lst, start, backward ? start - end : end - start, ns, backward ); if (e + e1 < _effort) { e += e1; start = end; } else if (e + e1 == _effort) { e += e1; match = true; } else if (e + e1 > _effort) { end = start; break; } //debugPlan<<"duration(s)["<logDebug( "Seconds: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' ); #endif for (int i=0; !match && i < 1000; ++i) { //milliseconds end.setTime(end.time().addMSecs(inc)); e1 = effort( lst, start, backward ? start - end : end - start, ns, backward ); if (e + e1 < _effort) { e += e1; start = end; } else if (e + e1 == _effort) { e += e1; match = true; } else if (e + e1 > _effort) { break; } //debugPlan<<"duration(ms)["<logError( i18n( "Could not match effort. Want: %1 got: %2", _effort.toString( Duration::Format_Hour ), e.toString( Duration::Format_Hour ) ) ); foreach (ResourceRequest *r, lst) { Resource *res = r->resource(); ns->logInfo( i18n( "Resource %1 available from %2 to %3", res->name(), locale.toString(r->availableFrom(), QLocale::ShortFormat), locale.toString(r->availableUntil(), QLocale::ShortFormat) ) ); } } DateTime t; if (e != Duration::zeroDuration) { foreach ( ResourceRequest *r, lst ) { DateTime tt; if ( backward ) { tt = r->availableAfter(end, ns); if ( tt.isValid() && ( ! t.isValid() || tt < t ) ) { t = tt; } } else { tt = r->availableBefore(end, ns); if ( tt.isValid() && ( ! t.isValid() || tt > t ) ) { t = tt; } } } } end = t.isValid() ? t : time; //debugPlan<<"<---"<<(backward?"(B)":"(F)")<<":"<time?end-time:time-end); } } //KPlato namespace + +QDebug operator<<(QDebug dbg, KPlato::Resource *r) +{ + if (!r) { return dbg << "Resource[0x0]"; } + dbg << "Resource[" << r->type(); + dbg << (r->name().isEmpty() ? r->id() : r->name()); + dbg << ']'; + return dbg; +} diff --git a/src/libs/kernel/kptresource.h b/src/libs/kernel/kptresource.h index e46b6c6e..287b4919 100644 --- a/src/libs/kernel/kptresource.h +++ b/src/libs/kernel/kptresource.h @@ -1,904 +1,906 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004-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 KPTRESOURCE_H #define KPTRESOURCE_H #include "plankernel_export.h" #include "kptglobal.h" #include "kptduration.h" #include "kptdatetime.h" #include "kptappointment.h" #include "kptcalendar.h" #include #include #include #include /// The main namespace. namespace KPlato { class Account; class Risk; class Effort; class Appointment; class Task; class Node; class Project; class Resource; class ResourceRequest; class ResourceGroupRequest; class ResourceRequestCollection; class Schedule; class ResourceSchedule; class Schedule; class XMLLoaderObject; class DateTimeInterval; /** * This class represents a group of similar resources to be assigned to a task * e.g. The list of employees, computer resources, etc */ /* IDEA; lets create a resourceGroup that has the intelligence to import PIM schedules * from the kroupware project and use the schedules to use the factory pattern to build * Resources (probably a derived class) which returns values on getFirstAvailableTime * and friends based on the schedules we got from the PIM projects. * (Thomas Zander mrt-2003 by suggestion of Shaheed) */ class PLANKERNEL_EXPORT ResourceGroup : public QObject { Q_OBJECT public: /// Default constructor explicit ResourceGroup(); explicit ResourceGroup( const ResourceGroup *group ); ~ResourceGroup(); enum Type { Type_Work, Type_Material }; QString id() const { return m_id; } void setId( const QString& id ); Project *project() { return m_project; } void setName( const QString& n ); const QString &name() const { return m_name;} void setType( Type type ); void setType(const QString &type); Type type() const { return m_type; } QString typeToString( bool trans = false ) const; static QStringList typeToStringList( bool trans = false ); bool isScheduled() const; /// Return true if any resource in this group is baselined bool isBaselined( long id = BASELINESCHEDULE ) const; /** Manage the resources in this list *

At some point we will have to look at not mixing types of resources * (e.g. you can't add a person to a list of computers * *

Risks must always be associated with a resource, so there is no option * to manipulate risks (@ref Risk) separately */ void addResource( int index, Resource*, Risk* ); Resource *takeResource( Resource *resource ); QList resources() const { return m_resources; } int indexOf( const Resource *resource ) const; Resource *resourceAt( int pos ) const { return m_resources.value( pos ); } int numResources() const { return m_resources.count(); } Risk* getRisk( int ); /** Manage the dependent resources. This is a list of the resource * groups that must have available resources for this resource to * perform the work *

see also @ref getRequiredResource, @ref getRequiredResource */ void addRequiredResource( ResourceGroup* ); /** Manage the dependent resources. This is a list of the resource * groups that must have available resources for this resource to * perform the work *

see also @ref addRequiredResource, @ref getRequiredResource */ ResourceGroup* getRequiredResource( int ); /** Manage the dependent resources. This is a list of the resource * groups that must have available resources for this resource to * perform the work *

see also @ref getRequiredResource, @ref addRequiredResource */ void deleteRequiredResource( int ); bool load( KoXmlElement &element, XMLLoaderObject &status ); void save( QDomElement &element ) const; /// Save workpackage document. Include only resources listed in @p lst void saveWorkPackageXML( QDomElement &element, const QList &lst ) const; void initiateCalculation( Schedule &sch ); void addNode( Node *node ) { m_nodes.append( node ); } void clearNodes() { m_nodes.clear(); } Calendar *defaultCalendar() { return m_defaultCalendar; } int units() const; void registerRequest( ResourceGroupRequest *request ) { m_requests.append( request ); } void unregisterRequest( ResourceGroupRequest *request ) { int i = m_requests.indexOf( request ); if ( i != -1 ) m_requests.removeAt( i ); } const QList &requests() const { return m_requests; } ResourceGroup *findId() const { return findId( m_id ); } ResourceGroup *findId( const QString &id ) const; bool removeId() { return removeId( m_id ); } bool removeId( const QString &id ); void insertId( const QString &id ); Appointment appointmentIntervals() const; // m_project is set when the resourcegroup is added to the project, // and reset when the resourcegroup is removed from the project void setProject( Project *project ); void copy( const ResourceGroup *group ); DateTime startTime( long id ) const; DateTime endTime( long id ) const; void blockChanged(bool on = true); /// A resource can be local to this project, or /// defined externally and shared with other projects bool isShared() const; /// Set resource to be shared if on = true, or local if on = false void setShared(bool on); #ifndef NDEBUG void printDebug( const QString& ident ); #endif protected: virtual void changed(); private: Project *m_project; QString m_id; // unique id QString m_name; QList m_resources; QList m_risks; QList m_requires; QList m_nodes; //The nodes that want resources from us Calendar *m_defaultCalendar; Type m_type; QList m_requests; bool m_blockChanged; bool m_shared; }; /** * Any resource that is used by a task. A resource can be a worker, or maybe wood. * If the resources is a worker or a piece of equipment which can be reused but * can only be used by one node in time, then we can use the scheduling methods of the * resource to schedule the resource available time for the project. * The Idea is that all nodes which need this resource point to it and the scheduling * code (partly implemented here) schedules the actual usage. * See also @ref ResourceGroup */ class PLANKERNEL_EXPORT Resource : public QObject { Q_OBJECT public: Resource(); explicit Resource(Resource *resource); virtual ~Resource(); QString id() const { return m_id; } void setId( const QString& id ); enum Type { Type_Work, Type_Material, Type_Team }; void setType( Type type ); void setType( const QString &type ); Type type() const { return m_type; } QString typeToString( bool trans = false ) const; static QStringList typeToStringList( bool trans = false ); void setName( const QString &n ); const QString &name() const { return m_name;} void setInitials( const QString &initials ); const QString &initials() const { return m_initials;} void setEmail( const QString &email ); const QString &email() const { return m_email;} /// Returns true if this resource will be allocated by default to new tasks bool autoAllocate() const; /// Set if this resource will be allocated by default to new tasks void setAutoAllocate( bool on ); void copy( Resource *resource ); void setParentGroup( ResourceGroup *parent ) { m_parent = parent; } ResourceGroup *parentGroup() const { return m_parent; } /// Set the time from when the resource is available to this project void setAvailableFrom( const DateTime &af ) { m_availableFrom = af; changed(); } /// Return the time when the resource is available to this project const DateTime &availableFrom() const { return m_availableFrom;} /// Set the time when the resource is no longer available to this project void setAvailableUntil( const DateTime &au ) { m_availableUntil = au; changed(); } /// Return the time when the resource is no longer available to this project. const DateTime &availableUntil() const { return m_availableUntil;} DateTime firstAvailableAfter( const DateTime &time, const DateTime &limit ) const; DateTime getBestAvailableTime( const Duration &duration ); DateTime getBestAvailableTime( const DateTime &after, const Duration &duration ); bool load( KoXmlElement &element, XMLLoaderObject &status ); void save( QDomElement &element ) const; /// Return the list of appointments for schedule @p id. QList appointments( long id = -1 ) const; /// Return the number of appointments (nodes) int numAppointments( long id = -1 ) const { return appointments( id ).count(); } /// Return the appointment at @p index for schedule @p id Appointment *appointmentAt( int index, long id = -1 ) const { return appointments( id ).value( index ); } int indexOf( Appointment *a, long id = -1 ) const { return appointments( id ).indexOf( a ); } /// Adds appointment to current schedule virtual bool addAppointment( Appointment *appointment ); /// Adds appointment to schedule sch virtual bool addAppointment( Appointment *appointment, Schedule &main ); /// Adds appointment to both this resource and node virtual void addAppointment( Schedule *node, const DateTime &start, const DateTime &end, double load = 100 ); void initiateCalculation( Schedule &sch ); bool isAvailable( Task *task ); void makeAppointment( Schedule *schedule, int load, const QList &required = QList() ); bool isOverbooked() const; /// check if overbooked on date. bool isOverbooked( const QDate &date ) const; /// check if overbooked within the interval start, end. bool isOverbooked( const DateTime &start, const DateTime &end ) const; double normalRate() const { return cost.normalRate; } void setNormalRate( double rate ) { cost.normalRate = rate; changed(); } double overtimeRate() const { return cost.overtimeRate; } void setOvertimeRate( double rate ) { cost.overtimeRate = rate; changed(); } /** * Return available units in percent */ int units() const { return m_units; } /** * Set available units in percent */ void setUnits( int units ); Project *project() const { return m_project; } /// Return the resources timespec. Defaults to local. QTimeZone timeZone() const; /** * Get the calendar for this resource. * Working resources may have a default calendar if the a calendar is marked as default, * this is checked if local=false. * If no calendar can be found for a working resource, the resource is not available. * * Material resources must have calendar explicitly set. * If there is no calendar set for a material resource, the resource is always available. */ Calendar *calendar( bool local = false ) const; //Calendar *calendar( const QString& id ) const; void setCalendar( Calendar *calendar ); /// Delete all requests for me void removeRequests(); /** * Used to clean up requests when the resource is deleted. */ void registerRequest( ResourceRequest *request ) { m_requests.append( request ); } void unregisterRequest( ResourceRequest *request ) { int i = m_requests.indexOf( request ); if ( i != -1 ) m_requests.removeAt( i ); } const QList &requests() const { return m_requests; } /// Returns a list of work intervals in the interval @p from, @p until. /// Appointments are subtracted if @p schedule is not 0 and overbooking is not allowed. AppointmentIntervalList workIntervals( const DateTime &from, const DateTime &until, Schedule *schedule ) const; /// Returns a list of work intervals in the interval @p from, @p until. AppointmentIntervalList workIntervals( const DateTime &from, const DateTime &until ) const; /// Updates work interval cache a list of work intervals extracted from the resource calendar /// with @p load in the interval @p from, @p until. /// The load of the intervals is set to m_units /// Note: The list may contain intervals outside @p from, @p until void calendarIntervals( const DateTime &from, const DateTime &until ) const; /// Load cache from @p element bool loadCalendarIntervalsCache( const KoXmlElement& element, KPlato::XMLLoaderObject& status ); /// Save cache to @p element void saveCalendarIntervalsCache( QDomElement &element ) const; /// Returns the effort that can be done starting at @p start within @p duration. /// The current schedule is used to check for appointments. /// If @p backward is true, checks backward in time. Duration effort( const DateTime &start, const Duration &duration, int units = 100, bool backward = false, const QList &required = QList() ) const; /// Returns the effort that can be done starting at @p start within @p duration. /// The schedule @p sch is used to check for appointments. /// If @p backward is true, checks backward in time. /// Status is returned in @p ok Duration effort( KPlato::Schedule* sch, const DateTime &start, const Duration& duration, int units = 100, bool backward = false, const QList< Resource* >& required = QList() ) const; /** * Find the first available time after @p time, within @p limit. * Returns invalid DateTime if not available. * Uses the current schedule to check for appointments. */ DateTime availableAfter( const DateTime &time, const DateTime &limit = DateTime() ) const; /** * Find the first available time before @p time, within @p limit. * Returns invalid DateTime if not available. * Uses the current schedule to check for appointments. */ DateTime availableBefore( const DateTime &time, const DateTime &limit = DateTime()) const; /** * Find the first available time after @p time, within @p limit. * Returns invalid DateTime if not available. * If @p sch == 0, Appointments are not checked. */ DateTime availableAfter( const DateTime &time, const DateTime &limit, Schedule *sch ) const; /** * Find the first available time before @p time, within @p limit. * Returns invalid DateTime if not available. * If @p sch == 0, Appointments are not checked. */ DateTime availableBefore( const DateTime &time, const DateTime &limit, Schedule *sch ) const; Resource *findId() const { return findId( m_id ); } Resource *findId( const QString &id ) const; bool removeId() { return removeId( m_id ); } bool removeId( const QString &id ); void insertId( const QString &id ); Calendar *findCalendar( const QString &id ) const; Appointment appointmentIntervals( long id ) const; Appointment appointmentIntervals() const; EffortCostMap plannedEffortCostPrDay( const QDate &start, const QDate &end, long id, EffortCostCalculationType = ECCT_All ); Duration plannedEffort( const QDate &date, EffortCostCalculationType = ECCT_All ) const; void setCurrentSchedulePtr( Schedule *schedule ) { m_currentSchedule = schedule; } void setCurrentSchedule( long id ) { m_currentSchedule = findSchedule( id ); } Schedule *currentSchedule() const { return m_currentSchedule; } bool isScheduled() const; QHash schedules() const { return m_schedules; } /** * Return schedule with @p id * If @p id == CURRENTSCHEDULE, return m_currentSchedule * Return 0 if schedule with @p id doesn't exist. */ Schedule *schedule( long id = CURRENTSCHEDULE ) const; /// Returns true if schedule with @p id is baselined. /// if Team resource, if any of the team members is baselined /// By default returns true if any schedule is baselined bool isBaselined( long id = BASELINESCHEDULE ) const; /** * Return schedule with @p id * Return 0 if schedule with @p id doesn't exist. */ Schedule *findSchedule( long id ) const; /// Take, and delete. void deleteSchedule( Schedule *schedule ); /// Take, don't delete. void takeSchedule( const Schedule *schedule ); void addSchedule( Schedule *schedule ); ResourceSchedule *createSchedule( const QString& name, int type, long id ); ResourceSchedule *createSchedule( Schedule *parent ); // m_project is set when the resource (or the parent) is added to the project, // and reset when the resource is removed from the project void setProject( Project *project ); void addExternalAppointment( const QString &id, Appointment *a ); void addExternalAppointment( const QString &id, const QString &name, const DateTime &from, const DateTime &end, double load = 100 ); void subtractExternalAppointment( const QString &id, const DateTime &from, const DateTime &end, double load ); void clearExternalAppointments(); void clearExternalAppointments( const QString &id ); /// Take the external appointments with identity @p id from the list of external appointments Appointment *takeExternalAppointment( const QString &id ); /// Return external appointments with identity @p id AppointmentIntervalList externalAppointments( const QString &id ); AppointmentIntervalList externalAppointments( const DateTimeInterval &interval = DateTimeInterval() ) const; int numExternalAppointments() const { return m_externalAppointments.count(); } QList externalAppointmentList() const { return m_externalAppointments.values(); } /// return a map of project id, project name QMap externalProjects() const; /// Return a measure of how suitable the resource is for allocation long allocationSuitability( const DateTime &time, const Duration &duration, bool backward ); DateTime startTime( long id ) const; DateTime endTime( long id ) const; /// Returns the list of required resources. /// Note: This list is used as default for allocation dialog, not for scheduling. QList requiredResources() const; /// Set the list of the required resources's ids so they can be resolved when used /// A required resource may not exist in the project yet void setRequiredIds( const QStringList &lst ); /// Add a resource id to the required ids list void addRequiredId( const QString &id ); /// Returns the list of required resource ids. QStringList requiredIds() const { return m_requiredIds; } /// Return the list of team members. QList teamMembers() const; /// Return the list of team members. QStringList teamMemberIds() const; /// Clear the list of team members. void clearTeamMembers() { m_teamMembers.clear(); } /// Add resource @p id to the list of team members. void addTeamMemberId( const QString &id ); /// Remove resource @p id from the list of team members. void removeTeamMemberId( const QString &id ); /// Set the list of team members to @p ids void setTeamMemberIds(const QStringList &ids); /// Return the account Account *account() const { return cost.account; } /// Set the @p account void setAccount( Account *account ); void blockChanged(bool on = true); /// A resource group can be local to this project, or /// defined externally and shared with other projects bool isShared() const; /// Set resource group to be shared if on = true, or local if on = false void setShared(bool on); // for xml loading code class WorkInfoCache { public: WorkInfoCache() { clear(); } void clear() { start = end = DateTime(); effort = Duration::zeroDuration; intervals.clear(); version = -1; } bool isValid() const { return start.isValid() && end.isValid(); } DateTime firstAvailableAfter( const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch ) const; DateTime firstAvailableBefore( const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch ) const; DateTime start; DateTime end; Duration effort; AppointmentIntervalList intervals; int version; bool load( const KoXmlElement& element, KPlato::XMLLoaderObject& status ); void save( QDomElement &element ) const; }; const WorkInfoCache &workInfoCache() const { return m_workinfocache; } Q_SIGNALS: void externalAppointmentToBeAdded(KPlato::Resource *r, int row); void externalAppointmentAdded(KPlato::Resource*, KPlato::Appointment*); void externalAppointmentToBeRemoved(KPlato::Resource *r, int row); void externalAppointmentRemoved(); void externalAppointmentChanged(KPlato::Resource *r, KPlato::Appointment *a); protected: DateTimeInterval requiredAvailable(Schedule *node, const DateTime &start, const DateTime &end ) const; void makeAppointment( Schedule *node, const DateTime &from, const DateTime &end, int load, const QList &required = QList() ); virtual void changed(); private: Project *m_project; ResourceGroup *m_parent; QHash m_schedules; QString m_id; // unique id QString m_name; QString m_initials; QString m_email; bool m_autoAllocate; DateTime m_availableFrom; DateTime m_availableUntil; QMap m_externalAppointments; int m_units; // available units in percent Type m_type; struct Cost { double normalRate; double overtimeRate; double fixed ; Account *account; } cost; Calendar *m_calendar; QList m_requests; QStringList m_requiredIds; QStringList m_teamMembers; Schedule *m_currentSchedule; mutable WorkInfoCache m_workinfocache; // return this if resource has no calendar and is a material resource Calendar m_materialCalendar; bool m_blockChanged; bool m_shared; #ifndef NDEBUG public: void printDebug( const QString& ident ); #endif }; PLANKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::Resource::WorkInfoCache &c ); /** * Risk is associated with a resource/task pairing to indicate the planner's confidence in the * estimated effort. Risk can be one of none, low, or high. Some factors that may be taken into * account for risk are the experience of the person and the reliability of equipment. */ class Risk { public: enum RiskType { NONE = 0, LOW = 1, HIGH = 2 }; Risk( Node *n, Resource *r, RiskType rt = NONE ); ~Risk(); RiskType riskType() { return m_riskType; } Node *node() { return m_node; } Resource *resource() { return m_resource; } private: Node *m_node; Resource *m_resource; RiskType m_riskType; }; class PLANKERNEL_EXPORT ResourceRequest { public: explicit ResourceRequest( Resource *resource = 0, int units = 1 ); explicit ResourceRequest( const ResourceRequest &r ); ~ResourceRequest(); ResourceGroupRequest *parent() const { return m_parent; } void setParent( ResourceGroupRequest *parent ) { m_parent = parent; } Resource *resource() const { return m_resource; } void setResource( Resource* resource ) { m_resource = resource; } bool load( KoXmlElement &element, Project &project ); void save( QDomElement &element ) const; /** * Get amount of requested resource units in percent */ int units() const; void setUnits( int value ); void registerRequest() { if ( m_resource ) m_resource->registerRequest( this ); } void unregisterRequest() { if ( m_resource ) m_resource->unregisterRequest( this ); } void makeAppointment( Schedule *schedule, int amount ); void makeAppointment( Schedule *schedule ); Task *task() const; /// Return the datetime from when the resource is available. /// If it is not valid, the project constraint start time is used. /// For teams the earliest time for any team member is used. DateTime availableFrom(); /// Return the datetime until when the resource is available. /// If it is not valid, the project constraint end time is used. /// For teams the latest time for any team member is used. DateTime availableUntil(); Schedule *resourceSchedule( Schedule *ns, Resource *resource = 0 ); DateTime availableAfter(const DateTime &time, Schedule *ns); DateTime availableBefore(const DateTime &time, Schedule *ns); Duration effort( const DateTime &time, const Duration &duration, Schedule *ns, bool backward ); DateTime workTimeAfter(const DateTime &dt, Schedule *ns = 0); DateTime workTimeBefore(const DateTime &dt, Schedule *ns = 0); /// Resource is allocated dynamically by the group request bool isDynamicallyAllocated() const { return m_dynamic; } /// Set resource is allocated dynamically void setAllocatedDynaically( bool dyn ) { m_dynamic = dyn; } /// Return a measure of how suitable the resource is for allocation long allocationSuitability( const DateTime &time, const Duration &duration, Schedule *ns, bool backward ); /// Returns a list of all the required resources that will be used in scheduling. /// Note: This list overrides the resources own list which is just used as default for allocation dialog. QList requiredResources() const { return m_required; } /// Set the list of required resources that will be used in scheduling. void setRequiredResources( const QList &lst ) { m_required = lst; } private: friend class ResourceGroupRequest; QList teamMembers() const; protected: void changed(); void setCurrentSchedulePtr( Schedule *ns ); void setCurrentSchedulePtr( Resource *resource, Schedule *ns ); private: Resource *m_resource; int m_units; ResourceGroupRequest *m_parent; bool m_dynamic; QList m_required; mutable QList m_teamMembers; #ifndef NDEBUG public: void printDebug( const QString& ident ); #endif }; QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest *r ); QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest &r ); class PLANKERNEL_EXPORT ResourceGroupRequest { public: explicit ResourceGroupRequest( ResourceGroup *group = 0, int units = 0 ); explicit ResourceGroupRequest( const ResourceGroupRequest &group ); ~ResourceGroupRequest(); void setParent( ResourceRequestCollection *parent ) { m_parent = parent;} ResourceRequestCollection *parent() const { return m_parent; } ResourceGroup *group() const { return m_group; } void setGroup( ResourceGroup *group ) { m_group = group; } void unregister( const ResourceGroup *group ) { if ( group == m_group ) m_group = 0; } /// Return a list of resource requests. /// If @p resolveTeam is true, include the team members, /// if @p resolveTeam is false, include the team resource itself. QList resourceRequests( bool resolveTeam=true ) const; void addResourceRequest( ResourceRequest *request ); void deleteResourceRequest( ResourceRequest *request ); int count() const { return m_resourceRequests.count(); } ResourceRequest *requestAt( int idx ) const { return m_resourceRequests.value( idx ); } ResourceRequest *takeResourceRequest( ResourceRequest *request ); ResourceRequest *find( const Resource *resource ) const; ResourceRequest *resourceRequest( const QString &name ); /// Return a list of allocated resources, allocation to group is not included by default. QStringList requestNameList( bool includeGroup = false ) const; /// Return a list of allocated resources. /// Allocations to groups are not included. /// Team resources are included but *not* the team members. /// Any dynamically allocated resource is not included. QList requestedResources() const; bool load( KoXmlElement &element, XMLLoaderObject &status ); void save( QDomElement &element ) const; /// The number of requested resources int units() const; void setUnits( int value ) { m_units = value; changed(); } /** * Returns the duration needed to do the @p effort starting at @p start. */ Duration duration( const DateTime &start, const Duration &effort, Schedule *ns, bool backward = false ); DateTime availableAfter( const DateTime &time, Schedule *ns ); DateTime availableBefore( const DateTime &time, Schedule *ns ); DateTime workTimeAfter(const DateTime &dt, Schedule *ns = 0); DateTime workTimeBefore(const DateTime &dt, Schedule *ns = 0); /** * Makes appointments for schedule @p schedule to the * requested resources for the duration found in @ref duration(). * @param schedule the schedule */ void makeAppointments( Schedule *schedule ); /** * Reserves the requested resources for the specified interval */ void reserve( const DateTime &start, const Duration &duration ); bool isEmpty() const; Task *task() const; void changed(); /// Reset dynamic resource allocations void resetDynamicAllocations(); /// Allocate dynamic requests. Do nothing if already allocated. void allocateDynamicRequests( const DateTime &time, const Duration &effort, Schedule *ns, bool backward ); private: ResourceGroup *m_group; int m_units; ResourceRequestCollection *m_parent; QList m_resourceRequests; DateTime m_start; Duration m_duration; #ifndef NDEBUG public: void printDebug( const QString& ident ); #endif }; class PLANKERNEL_EXPORT ResourceRequestCollection { public: explicit ResourceRequestCollection( Task *task = 0 ); ~ResourceRequestCollection(); QList requests() const { return m_requests; } void addRequest( ResourceGroupRequest *request ); void deleteRequest( ResourceGroupRequest *request ) { int i = m_requests.indexOf( request ); if ( i != -1 ) m_requests.removeAt( i ); delete request; changed(); } int takeRequest( ResourceGroupRequest *request ) { int i = m_requests.indexOf( request ); if ( i != -1 ) { m_requests.removeAt( i ); changed(); } return i; } ResourceGroupRequest *find( const ResourceGroup *resource ) const; ResourceRequest *find( const Resource *resource ) const; ResourceRequest *resourceRequest( const QString &name ) const; /// The ResourceRequestCollection has no requests bool isEmpty() const; /// Empty the ResourceRequestCollection of all requets void clear() { m_requests.clear(); } /// Reset dynamic resource allocations void resetDynamicAllocations(); bool contains( const QString &identity ) const; ResourceGroupRequest *findGroupRequestById( const QString &id ) const; /// Return a list of names of allocated resources. /// Allocations to groups are not included by default. /// Team resources are included but *not* the team members. /// Any dynamically allocated resource is not included. QStringList requestNameList( bool includeGroup = false ) const; /// Return a list of allocated resources. /// Allocations to groups are not included. /// Team resources are included but *not* the team members. /// Any dynamically allocated resource is not included. QList requestedResources() const; /// Return a list of all resource requests. /// If @p resolveTeam is true, include the team members, /// if @p resolveTeam is false, include the team resource itself. QList resourceRequests( bool resolveTeam=true ) const; //bool load(KoXmlElement &element, Project &project); void save( QDomElement &element ) const; /** * Returns the duration needed to do the @p effort starting at @p time. */ Duration duration( const DateTime &time, const Duration &effort, Schedule *sch, bool backward = false ); DateTime availableAfter( const DateTime &time, Schedule *ns ); DateTime availableBefore( const DateTime &time, Schedule *ns ); DateTime workTimeAfter(const DateTime &dt, Schedule *ns = 0) const; DateTime workTimeBefore(const DateTime &dt, Schedule *ns = 0) const; DateTime workStartAfter(const DateTime &time, Schedule *ns); DateTime workFinishBefore(const DateTime &time, Schedule *ns); /** * Makes appointments for the schedule @p schedule to the requested resources. * Assumes that @ref duration() has been run. * @param schedule the schedule */ void makeAppointments( Schedule *schedule ); /** * Reserves the requested resources for the specified interval */ void reserve( const DateTime &start, const Duration &duration ); Task *task() const { return m_task; } void setTask( Task *t ) { m_task = t; } void changed(); Duration effort( const QList &lst, const DateTime &time, const Duration &duration, Schedule *ns, bool backward ) const; int numDays(const QList &lst, const DateTime &time, bool backward) const; Duration duration(const QList &lst, const DateTime &time, const Duration &_effort, Schedule *ns, bool backward); private: Task *m_task; QList m_requests; }; } //KPlato namespace +PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::Resource *r); + #endif