diff options
Diffstat (limited to 'kplato/kptappointment.cc')
-rw-r--r-- | kplato/kptappointment.cc | 734 |
1 files changed, 734 insertions, 0 deletions
diff --git a/kplato/kptappointment.cc b/kplato/kptappointment.cc new file mode 100644 index 00000000..897ab390 --- /dev/null +++ b/kplato/kptappointment.cc @@ -0,0 +1,734 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Dag Andersen <[email protected]> + + 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; + version 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kptappointment.h" + +#include "kptproject.h" +#include "kpttask.h" +#include "kptdatetime.h" +#include "kptcalendar.h" +#include "kpteffortcostmap.h" +#include "kptschedule.h" + +#include <kdebug.h> + +namespace KPlato +{ + +class Resource; + +AppointmentInterval::AppointmentInterval() { + m_load = 100.0; +} +AppointmentInterval::AppointmentInterval(const AppointmentInterval &interval) { + //kdDebug()<<k_funcinfo<<endl; + m_start = interval.startTime(); + m_end = interval.endTime(); + m_load = interval.load(); +} +AppointmentInterval::AppointmentInterval(const DateTime &start, const DateTime end, double load) { + //kdDebug()<<k_funcinfo<<endl; + m_start = start; + m_end = end; + m_load = load; +} +AppointmentInterval::~AppointmentInterval() { + //kdDebug()<<k_funcinfo<<endl; +} + +Duration AppointmentInterval::effort(const DateTime &start, const DateTime end) const { + if (start >= m_end || end <= m_start) { + return Duration::zeroDuration; + } + DateTime s = (start > m_start ? start : m_start); + DateTime e = (end < m_end ? end : m_end); + return (e - s) * m_load / 100; +} + +Duration AppointmentInterval::effort(const DateTime &time, bool upto) const { + if (upto) { + if (time <= m_start) { + return Duration::zeroDuration; + } + DateTime e = (time < m_end ? time : m_end); + return (e - m_start) * m_load / 100; + } + // from time till end + if (time >= m_end) { + return Duration::zeroDuration; + } + DateTime s = (time > m_start ? time : m_start); + return (m_end - s) * m_load / 100; +} + +bool AppointmentInterval::loadXML(QDomElement &element) { + //kdDebug()<<k_funcinfo<<endl; + bool ok; + QString s = element.attribute("start"); + if (s != "") + m_start = DateTime::fromString(s); + s = element.attribute("end"); + if (s != "") + m_end = DateTime::fromString(s); + m_load = element.attribute("load", "100").toDouble(&ok); + if (!ok) m_load = 100; + return m_start.isValid() && m_end.isValid(); +} + +void AppointmentInterval::saveXML(QDomElement &element) const { + QDomElement me = element.ownerDocument().createElement("interval"); + element.appendChild(me); + + me.setAttribute("start", m_start.toString(Qt::ISODate)); + me.setAttribute("end", m_end.toString(Qt::ISODate)); + me.setAttribute("load", m_load); +} + +bool AppointmentInterval::isValid() const { + return m_start.isValid() && m_end.isValid(); +} + +AppointmentInterval AppointmentInterval::firstInterval(const AppointmentInterval &interval, const DateTime &from) const { + //kdDebug()<<k_funcinfo<<interval.startTime().toString()<<" - "<<interval.endTime().toString()<<" from="<<from.toString()<<endl; + DateTime f = from; + DateTime s1 = m_start; + DateTime e1 = m_end; + DateTime s2 = interval.startTime(); + DateTime e2 = interval.endTime(); + AppointmentInterval a; + if (f.isValid() && f >= e1 && f >= e2) { + return a; + } + if (f.isValid()) { + if (s1 < f && f < e1) { + s1 = f; + } + if (s2 < f && f < e2) { + s2 = f; + } + } else { + f = s1 < s2 ? s1 : s2; + } + if (s1 < s2) { + a.setStartTime(s1); + if (e1 <= s2) { + a.setEndTime(e1); + } else { + a.setEndTime(s2); + } + a.setLoad(m_load); + } else if (s1 > s2) { + a.setStartTime(s2); + if (e2 <= s1) { + a.setEndTime(e2); + } else { + a.setEndTime(s1); + } + a.setLoad(interval.load()); + } else { + a.setStartTime(s1); + if (e1 <= e2) + a.setEndTime(e1); + else + a.setEndTime(e2); + a.setLoad(m_load + interval.load()); + } + //kdDebug()<<k_funcinfo<<a.startTime().toString()<<" - "<<a.endTime().toString()<<" load="<<a.load()<<endl; + return a; +} + +////// + +Appointment::UsedEffortItem::UsedEffortItem(QDate date, Duration effort, bool overtime) { + m_date = date; + m_effort = effort; + m_overtime = overtime; +} +QDate Appointment::UsedEffortItem::date() { + return m_date; +} +Duration Appointment::UsedEffortItem::effort() { + return m_effort; +} +bool Appointment::UsedEffortItem::isOvertime() { + return m_overtime; +} + +Appointment::UsedEffort::UsedEffort() { + setAutoDelete(true); +} + +void Appointment::UsedEffort::inSort(QDate date, Duration effort, bool overtime) { + UsedEffortItem *item = new UsedEffortItem(date, effort, overtime); + QPtrList<UsedEffortItem>::inSort(item); +} + +Duration Appointment::UsedEffort::usedEffort(bool includeOvertime) const { + Duration eff; + QPtrListIterator<UsedEffortItem> it(*this); + for (; it.current(); ++it) { + if (includeOvertime || !it.current()->isOvertime()) { + eff += it.current()->effort(); + } + } + return eff; +} + +Duration Appointment::UsedEffort::usedEffort(const QDate &date, bool includeOvertime) const { + Duration eff; + QPtrListIterator<UsedEffortItem> it(*this); + for (; it.current(); ++it) { + if ((includeOvertime || !it.current()->isOvertime()) && + it.current()->date() == date) { + eff += it.current()->effort(); + } + } + return eff; +} + +Duration Appointment::UsedEffort::usedEffortTo(const QDate &date, bool includeOvertime) const { + Duration eff; + QPtrListIterator<UsedEffortItem> it(*this); + for (; it.current(); ++it) { + if ((includeOvertime || !it.current()->isOvertime()) && + it.current()->date() <= date) { + eff += it.current()->effort(); + } + } + return eff; +} + +Duration Appointment::UsedEffort::usedOvertime() const { + UsedEffortItem *item = getFirst(); + return item==0 ? Duration::zeroDuration : usedOvertime(item->date()); +} + +Duration Appointment::UsedEffort::usedOvertime(const QDate &date) const { + Duration eff; + QPtrListIterator<UsedEffortItem> it(*this); + for (; it.current(); ++it) { + if (it.current()->isOvertime() && it.current()->date() == date) { + eff += it.current()->effort(); + } + } + return eff; +} + +Duration Appointment::UsedEffort::usedOvertimeTo(const QDate &date) const { + Duration eff; + QPtrListIterator<UsedEffortItem> it(*this); + for (; it.current(); ++it) { + if (it.current()->isOvertime() && it.current()->date() <= date) { + eff += it.current()->effort(); + } + } + return eff; +} + +bool Appointment::UsedEffort::load(QDomElement &element) { + QString s; + QDomNodeList list = element.childNodes(); + for (unsigned int i=0; i<list.count(); ++i) { + if (list.item(i).isElement()) { + QDomElement e = list.item(i).toElement(); + if (e.tagName() == "actual-effort") { + QDate date; + s = e.attribute("date"); + if (s != "") + date = QDate::fromString(s, Qt::ISODate); + Duration eff = Duration::fromString(e.attribute("effort")); + bool ot = e.attribute("overtime", "0").toInt(); + if (date.isValid()) { + inSort(date, eff, ot); + } else { + kdError()<<k_funcinfo<<"Load failed, illegal date: "<<e.attribute("date")<<endl; + } + } + } + } + return true; +} + +void Appointment::UsedEffort::save(QDomElement &element) const { + if (isEmpty()) return; + QPtrListIterator<UsedEffortItem> it = *this; + for (; it.current(); ++it) { + QDomElement me = element.ownerDocument().createElement("actual-effort"); + element.appendChild(me); + me.setAttribute("date",it.current()->date().toString(Qt::ISODate)); + me.setAttribute("effort",it.current()->effort().toString()); + me.setAttribute("overtime",it.current()->isOvertime()); + } +} + +int Appointment::UsedEffort::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2) { + QDate d1 = static_cast<UsedEffortItem*>(item1)->date(); + QDate d2 = static_cast<UsedEffortItem*>(item2)->date(); + if (d1 > d2) return 1; + if (d1 < d2) return -1; + return 0; +} + +//// +Appointment::Appointment() + : m_extraRepeats(), m_skipRepeats() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + m_resource=0; + m_node=0; + m_repeatInterval=Duration(); + m_repeatCount=0; + + m_intervals.setAutoDelete(true); +} + +Appointment::Appointment(Schedule *resource, Schedule *node, DateTime start, DateTime end, double load) + : m_extraRepeats(), + m_skipRepeats() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + m_node = node; + m_resource = resource; + m_repeatInterval = Duration(); + m_repeatCount = 0; + + addInterval(start, end, load); + + m_intervals.setAutoDelete(true); +} + +Appointment::Appointment(Schedule *resource, Schedule *node, DateTime start, Duration duration, double load) + : m_extraRepeats(), + m_skipRepeats() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + m_node = node; + m_resource = resource; + m_repeatInterval = Duration(); + m_repeatCount = 0; + + addInterval(start, duration, load); + + m_intervals.setAutoDelete(true); +} + +Appointment::~Appointment() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + detach(); +} + +void Appointment::addInterval(AppointmentInterval *a) { + //kdDebug()<<k_funcinfo<<m_resource->name()<<" to "<<m_node->name()<<endl; + m_intervals.inSort(a); +} +void Appointment::addInterval(const DateTime &start, const DateTime &end, double load) { + addInterval(new AppointmentInterval(start, end, load)); +} +void Appointment::addInterval(const DateTime &start, const Duration &duration, double load) { + DateTime e = start+duration; + addInterval(start, e, load); +} + +double Appointment::maxLoad() const { + double v = 0.0; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + if (v < it.current()->load()) + v = it.current()->load(); + } + return v; +} + +DateTime Appointment::startTime() const { + DateTime t; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + if (!t.isValid() || t > it.current()->startTime()) + t = it.current()->startTime(); + } + return t; +} + +DateTime Appointment::endTime() const { + DateTime t; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + if (!t.isValid() || t < it.current()->endTime()) + t = it.current()->endTime(); + } + return t; +} + +void Appointment::deleteAppointmentFromRepeatList(DateTime) { +} + +void Appointment::addAppointmentToRepeatList(DateTime) { +} + +bool Appointment::isBusy(const DateTime &/*start*/, const DateTime &/*end*/) { + return false; +} + +bool Appointment::loadXML(QDomElement &element, Project &project, Schedule &sch) { + //kdDebug()<<k_funcinfo<<endl; + QDictIterator<Node> it = project.nodeDict(); +/* for (; it.current(); ++it) { + kdDebug()<<" Node="<<it.current()->name()<<" id="<<it.currentKey()<<endl; + }*/ + Node *node = project.findNode(element.attribute("task-id")); + if (node == 0) { + kdError()<<k_funcinfo<<"The referenced task does not exists: "<<element.attribute("task-id")<<endl; + return false; + } + Resource *res = project.resource(element.attribute("resource-id")); + if (res == 0) { + kdError()<<k_funcinfo<<"The referenced resource does not exists: resource id="<<element.attribute("resource-id")<<endl; + return false; + } + if (!res->addAppointment(this, sch)) { + kdError()<<k_funcinfo<<"Failed to add appointment to resource: "<<res->name()<<endl; + return false; + } + if (!node->addAppointment(this, sch)) { + kdError()<<k_funcinfo<<"Failed to add appointment to node: "<<node->name()<<endl; + m_resource->takeAppointment(this); + return false; + } + //kdDebug()<<k_funcinfo<<"res="<<m_resource<<" node="<<m_node<<endl; + QDomNodeList list = element.childNodes(); + for (unsigned int i=0; i<list.count(); ++i) { + if (list.item(i).isElement()) { + QDomElement e = list.item(i).toElement(); + if (e.tagName() == "interval") { + AppointmentInterval *a = new AppointmentInterval(); + if (a->loadXML(e)) { + addInterval(a); + } else { + kdError()<<k_funcinfo<<"Could not load interval"<<endl; + delete a; + } + } + } + } + if (m_intervals.isEmpty()) { + return false; + } + m_actualEffort.load(element); + return true; +} + +void Appointment::saveXML(QDomElement &element) const { + if (m_intervals.isEmpty()) { + kdError()<<k_funcinfo<<"Incomplete appointment data: No intervals"<<endl; + } + if (m_resource == 0 || m_resource->resource() == 0) { + kdError()<<k_funcinfo<<"Incomplete appointment data: No resource"<<endl; + return; + } + if (m_node == 0 || m_node->node() == 0) { + kdError()<<k_funcinfo<<"Incomplete appointment data: No node"<<endl; + return; // shouldn't happen + } + //kdDebug()<<k_funcinfo<<endl; + QDomElement me = element.ownerDocument().createElement("appointment"); + element.appendChild(me); + + me.setAttribute("resource-id", m_resource->resource()->id()); + me.setAttribute("task-id", m_node->node()->id()); + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + it.current()->saveXML(me); + } + m_actualEffort.save(me); +} + +// Returns the total actual effort for this appointment +Duration Appointment::plannedEffort() const { + Duration d; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(); + } + return d; +} + +// Returns the planned effort on the date +Duration Appointment::plannedEffort(const QDate &date) const { + Duration d; + DateTime s(date); + DateTime e(date.addDays(1)); + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(s, e); + } + return d; +} + +// Returns the planned effort upto and including the date +Duration Appointment::plannedEffortTo(const QDate& date) const { + Duration d; + DateTime e(date.addDays(1)); + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(e, true); + } + return d; +} + +// Returns a list of efforts pr day for interval start, end inclusive +// The list only includes days with any planned effort +EffortCostMap Appointment::plannedPrDay(const QDate& start, const QDate& end) const { + //kdDebug()<<k_funcinfo<<m_node->id()<<", "<<m_resource->id()<<endl; + EffortCostMap ec; + Duration eff; + DateTime dt(start); + DateTime ndt(dt.addDays(1)); + double rate = m_resource->normalRatePrHour(); + AppointmentIntervalListIterator it = m_intervals; + for (; it.current(); ++it) { + DateTime st = it.current()->startTime(); + DateTime e = it.current()->endTime(); + if (end < st.date()) + break; + if (dt.date() < st.date()) { + dt.setDate(st.date()); + } + ndt = dt.addDays(1); + while (dt.date() <= e.date()) { + eff = it.current()->effort(dt, ndt); + ec.add(dt.date(), eff, eff.toDouble(Duration::Unit_h) * rate); + if (dt.date() < e.date() ) { + // loop trough the interval (it spans dates) + dt = ndt; + ndt = ndt.addDays(1); + } else { + break; + } + } + } + return ec; +} + + +// Returns the total actual effort for this appointment +Duration Appointment::actualEffort() const { + return m_actualEffort.usedEffort(); +} + +// Returns the actual effort on the date +Duration Appointment::actualEffort(const QDate &date) const { + return m_actualEffort.usedEffort(date); +} + +// Returns the actual effort upto and including date +Duration Appointment::actualEffortTo(const QDate &date) const { + return m_actualEffort.usedEffortTo(date); +} + +double Appointment::plannedCost() { + if (m_resource && m_resource->resource()) { + return plannedEffort().toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime + } + return 0.0; +} + +//Calculates the planned cost on date +double Appointment::plannedCost(const QDate &date) { + if (m_resource && m_resource->resource()) { + return plannedEffort(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime + } + return 0.0; +} + +//Calculates the planned cost upto and including date +double Appointment::plannedCostTo(const QDate &date) { + if (m_resource && m_resource->resource()) { + return plannedEffortTo(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime + } + return 0.0; +} + +// Calculates the total actual cost for this appointment +double Appointment::actualCost() { + //kdDebug()<<k_funcinfo<<m_actualEffort.usedEffort(false /*ex. overtime*/).toDouble(Duration::Unit_h)<<endl; + if (m_resource && m_resource->resource()) { + return (m_actualEffort.usedEffort(false /*ex. overtime*/).toDouble(Duration::Unit_h)*m_resource->resource()->normalRate()) + (m_actualEffort.usedOvertime().toDouble(Duration::Unit_h)*m_resource->resource()->overtimeRate()); + } + return 0.0; +} + +// Calculates the actual cost on date +double Appointment::actualCost(const QDate &date) { + if (m_resource && m_resource->resource()) { + return (m_actualEffort.usedEffort(date, false /*ex. overtime*/).toDouble(Duration::Unit_h)*m_resource->resource()->normalRate()) + (m_actualEffort.usedOvertime(date).toDouble(Duration::Unit_h)*m_resource->resource()->overtimeRate()); + } + return 0.0; +} + +// Calculates the actual cost upto and including date +double Appointment::actualCostTo(const QDate &date) { + if (m_resource && m_resource->resource()) { + return (m_actualEffort.usedEffortTo(date, false /*ex. overtime*/).toDouble(Duration::Unit_h)*m_resource->resource()->normalRate()) + (m_actualEffort.usedOvertimeTo(date).toDouble(Duration::Unit_h)*m_resource->resource()->overtimeRate()); + } + return 0.0; +} + +void Appointment::addActualEffort(QDate date, Duration effort, bool overtime) { + m_actualEffort.inSort(date, effort, overtime); +} + +bool Appointment::attach() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + if (m_resource && m_node) { + m_resource->add(this); + m_node->add(this); + return true; + } + kdWarning()<<k_funcinfo<<"Failed: "<<(m_resource ? "" : "resource=0 ") + <<(m_node ? "" : "node=0")<<endl; + return false; +} + +void Appointment::detach() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + if (m_resource) { + m_resource->takeAppointment(this); // takes from node also + } + if (m_node) { + m_node->takeAppointment(this); // to make it robust + } +} + +// Returns the effort from start to end +Duration Appointment::effort(const DateTime &start, const DateTime &end) const { + Duration d; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(start, end); + } + return d; +} +// Returns the effort from start for the duration +Duration Appointment::effort(const DateTime &start, const Duration &duration) const { + Duration d; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(start, start+duration); + } + return d; +} +// Returns the effort upto time / from time +Duration Appointment::effortFrom(const DateTime &time) const { + Duration d; + QPtrListIterator<AppointmentInterval> it = m_intervals; + for (; it.current(); ++it) { + d += it.current()->effort(time, false); + } + return d; +} + +Appointment &Appointment::operator=(const Appointment &app) { + m_resource = app.resource(); + m_node = app.node(); + m_repeatInterval = app.repeatInterval(); + m_repeatCount = app.repeatCount(); + + m_intervals.clear(); + QPtrListIterator<AppointmentInterval> it = app.intervals(); + for (; it.current(); ++it) { + addInterval(new AppointmentInterval(*(it.current()))); + } + return *this; +} + +Appointment &Appointment::operator+=(const Appointment &app) { + *this = *this + app; + return *this; +} + +Appointment Appointment::operator+(const Appointment &app) { + Appointment a; + AppointmentIntervalList ai = app.intervals(); + AppointmentInterval i; + AppointmentInterval *i1 = m_intervals.first(); + AppointmentInterval *i2 = ai.first(); + DateTime from; + while (i1 || i2) { + if (!i1) { + if (!from.isValid() || from < i2->startTime()) + from = i2->startTime(); + a.addInterval(from, i2->endTime(), i2->load()); + //kdDebug()<<"Interval+ (i2): "<<from.toString()<<" - "<<i2->endTime().toString()<<endl; + from = i2->endTime(); + i2 = ai.next(); + continue; + } + if (!i2) { + if (!from.isValid() || from < i1->startTime()) + from = i1->startTime(); + a.addInterval(from, i1->endTime(), i1->load()); + //kdDebug()<<"Interval+ (i1): "<<from.toString()<<" - "<<i1->endTime().toString()<<endl; + from = i1->endTime(); + i1 = m_intervals.next(); + continue; + } + i = i1->firstInterval(*i2, from); + if (!i.isValid()) { + break; + } + a.addInterval(i); + from = i.endTime(); + //kdDebug()<<"Interval+ (i): "<<i.startTime().toString()<<" - "<<i.endTime().toString()<<" load="<<i.load()<<endl; + if (i1 && a.endTime() >= i1->endTime()) { + i1 = m_intervals.next(); + } + if (i2 && a.endTime() >= i2->endTime()) { + i2 = ai.next(); + } + } + return a; +} + +#ifndef NDEBUG +void Appointment::printDebug(QString indent) +{ + bool err = false; + if (m_node == 0) { + kdDebug()<<indent<<" No node schedule"<<endl; + err = true; + } else if (m_node->node() == 0) { + kdDebug()<<indent<<" No node"<<endl; + err = true; + } + if (m_resource == 0) { + kdDebug()<<indent<<" No resource schedule"<<endl; + err = true; + } else if (m_resource->resource() == 0) { + kdDebug()<<indent<<" No resource"<<endl; + err = true; + } + if (err) + return; + kdDebug()<<indent<<" + Appointment to schedule: "<<m_node->name()<<" ("<<m_node->type()<<")"<<" resource: "<<m_resource->resource()->name()<<endl; + indent += " ! "; + QPtrListIterator<AppointmentInterval> it = intervals(); + for (; it.current(); ++it) { + kdDebug()<<indent<<it.current()->startTime().toString()<<" - "<<it.current()->endTime().toString()<<" load="<<it.current()->load()<<endl; + } +} +#endif + +} //KPlato namespace |