diff options
Diffstat (limited to 'kplato/kptresource.cpp')
-rw-r--r-- | kplato/kptresource.cpp | 1260 |
1 files changed, 1260 insertions, 0 deletions
diff --git a/kplato/kptresource.cpp b/kplato/kptresource.cpp new file mode 100644 index 00000000..763c0c86 --- /dev/null +++ b/kplato/kptresource.cpp @@ -0,0 +1,1260 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Thomas zander <[email protected]> + Copyright (C) 2004, 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; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kptresource.h" +#include "kptappointment.h" +#include "kptproject.h" +#include "kpttask.h" +#include "kptdatetime.h" +#include "kptcalendar.h" +#include "kpteffortcostmap.h" +#include "kptschedule.h" + +#include <kdebug.h> +#include <tdeglobal.h> +#include <tdelocale.h> + +namespace KPlato +{ + +ResourceGroup::ResourceGroup(Project *project) { + m_project = project; + m_type = Type_Work; + m_resources.setAutoDelete(true); + generateId(); + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; +} + +ResourceGroup::~ResourceGroup() { + if (findId() == this) { + removeId(); // only remove myself (I may be just a working copy) + } + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; +} + +bool ResourceGroup::setId(TQString id) { + //kdDebug()<<k_funcinfo<<id<<endl; + if (id.isEmpty()) { + kdError()<<k_funcinfo<<"id is empty"<<endl; + m_id = id; + return false; + } + ResourceGroup *g = findId(); + if (g == this) { + //kdDebug()<<k_funcinfo<<"My id found, remove it"<<endl; + removeId(); + } else if (g) { + //Hmmm, shouldn't happen + kdError()<<k_funcinfo<<"My id '"<<m_id<<"' already used for different group: "<<g->name()<<endl; + } + if (findId(id)) { + kdError()<<k_funcinfo<<"id '"<<id<<"' is already used for different group: "<<findId(id)->name()<<endl; + m_id = TQString(); // hmmm + return false; + } + m_id = id; + insertId(id); + //kdDebug()<<k_funcinfo<<m_name<<": inserted id="<<id<<endl; + return true; +} + +void ResourceGroup::generateId() { + if (!m_id.isEmpty()) { + removeId(); + } + for (int i=0; i<32000 ; ++i) { + m_id = m_id.setNum(i); + if (!findId()) { + insertId(m_id); + return; + } + } + m_id = TQString(); +} + +void ResourceGroup::addResource(Resource* resource, Risk*) { + m_resources.append(resource); +} + +Resource* ResourceGroup::getResource(int) { + return 0L; +} + +Risk* ResourceGroup::getRisk(int) { + return 0L; +} + +void ResourceGroup::removeResource(Resource *resource) { + m_resources.removeRef(resource); +} + +Resource *ResourceGroup::takeResource(Resource *resource) { + return m_resources.take(m_resources.findRef(resource)); +} + +void ResourceGroup::removeResource(int) { +} + +void ResourceGroup::addRequiredResource(ResourceGroup*) { +} + +ResourceGroup* ResourceGroup::getRequiredResource(int) { + return 0L; +} + +void ResourceGroup::removeRequiredResource(int) { +} + +bool ResourceGroup::load(TQDomElement &element) { + //kdDebug()<<k_funcinfo<<endl; + setId(element.attribute("id")); + m_name = element.attribute("name"); + + TQDomNodeList list = element.childNodes(); + for (unsigned int i=0; i<list.count(); ++i) { + if (list.item(i).isElement()) { + TQDomElement e = list.item(i).toElement(); + if (e.tagName() == "resource") { + // Load the resource + Resource *child = new Resource(m_project); + if (child->load(e)) + addResource(child, 0); + else + // TODO: Complain about this + delete child; + } + } + } + return true; +} + +void ResourceGroup::save(TQDomElement &element) const { + //kdDebug()<<k_funcinfo<<endl; + + TQDomElement me = element.ownerDocument().createElement("resource-group"); + element.appendChild(me); + + me.setAttribute("id", m_id); + me.setAttribute("name", m_name); + + TQPtrListIterator<Resource> it(m_resources); + for ( ; it.current(); ++it ) { + it.current()->save(me); + } +} + +void ResourceGroup::initiateCalculation(Schedule &sch) { + TQPtrListIterator<Resource> it(m_resources); + for (; it.current(); ++it) { + it.current()->initiateCalculation(sch); + } + clearNodes(); +} + +int ResourceGroup::units() { + int u = 0; + TQPtrListIterator<Resource> it = m_resources; + for (; it.current(); ++it) { + u += it.current()->units(); + } + return u; +} + +ResourceGroup *ResourceGroup::findId(const TQString &id) const { + return m_project ? m_project->findResourceGroup(id) : 0; +} + +bool ResourceGroup::removeId(const TQString &id) { + return m_project ? m_project->removeResourceGroupId(id): false; +} + +void ResourceGroup::insertId(const TQString &id) { + if (m_project) + m_project->insertResourceGroupId(id, this); +} + +Appointment ResourceGroup::appointmentIntervals() const { + Appointment a; + TQPtrListIterator<Resource> it = m_resources; + for (; it.current(); ++it) { + a += it.current()->appointmentIntervals(); + } + return a; +} + +Resource::Resource(Project *project) : m_project(project), m_schedules(), m_workingHours() { + m_type = Type_Work; + m_units = 100; // % + + m_availableFrom = DateTime(TQDate::currentDate()); + m_availableUntil = TQDateTime(m_availableFrom.addYears(2)); + + cost.normalRate = 100; + cost.overtimeRate = 200; + cost.fixed = 0; + m_calendar = 0; + m_currentSchedule = 0; + generateId(); + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; +} + +Resource::Resource(Resource *resource) { + //kdDebug()<<k_funcinfo<<"("<<this<<") from ("<<resource<<")"<<endl; + copy(resource); +} + +Resource::~Resource() { + //kdDebug()<<k_funcinfo<<"("<<this<<")"<<endl; + if (findId() == this) { + removeId(); // only remove myself (I may be just a working copy) + } + TQPtrListIterator<ResourceRequest> it = m_requests; + for (; it.current(); ++it) { + it.current()->setResource(0); // avoid the request to mess with my list + } + m_requests.first(); + for (; m_requests.current(); m_requests.next()) { + m_requests.current()->parent()->removeResourceRequest(m_requests.current()); // deletes the request + } +} + +bool Resource::setId(TQString id) { + //kdDebug()<<k_funcinfo<<id<<endl; + if (id.isEmpty()) { + kdError()<<k_funcinfo<<"id is empty"<<endl; + m_id = id; + return false; + } + Resource *r = findId(); + if (r == this) { + //kdDebug()<<k_funcinfo<<"My id found, remove it"<<endl; + removeId(); + } else if (r) { + //Hmmm, shouldn't happen + kdError()<<k_funcinfo<<"My id '"<<m_id<<"' already used for different resource: "<<r->name()<<endl; + } + if (findId(id)) { + kdError()<<k_funcinfo<<"id '"<<id<<"' is already used for different resource: "<<findId(id)->name()<<endl; + m_id = TQString(); // hmmm + return false; + } + m_id = id; + insertId(id); + //kdDebug()<<k_funcinfo<<m_name<<": inserted id="<<id<<endl; + return true; +} + +void Resource::generateId() { + if (!m_id.isEmpty()) { + removeId(); + } + for (int i=0; i<32000 ; ++i) { + m_id = m_id.setNum(i); + if (!findId()) { + insertId(m_id); + //kdDebug()<<k_funcinfo<<m_name<<": inserted id="<<m_id<<endl; + return; + } + } + m_id = TQString(); +} + +void Resource::setType(const TQString &type) { + if (type == "Work") + m_type = Type_Work; + else if (type == "Material") + m_type = Type_Material; +} + +TQString Resource::typeToString() const { + if (m_type == Type_Work) + return TQString("Work"); + else if (m_type == Type_Material) + return TQString("Material"); + + return TQString(); +} + +void Resource::copy(Resource *resource) { + m_project = resource->project(); + //m_appointments = resource->appointments(); // Note + m_id = resource->id(); + m_name = resource->name(); + m_initials = resource->initials(); + m_email = resource->email(); + m_availableFrom = resource->availableFrom(); + m_availableUntil = resource->availableUntil(); + m_workingHours.clear(); + m_workingHours = resource->workingHours(); + + m_units = resource->units(); // available units in percent + + m_type = resource->type(); + + cost.normalRate = resource->normalRate(); + cost.overtimeRate = resource->overtimeRate(); + cost.fixed = resource->fixedCost(); + + m_calendar = resource->m_calendar; +} + +void Resource::addWorkingHour(TQTime from, TQTime until) { + //kdDebug()<<k_funcinfo<<endl; + m_workingHours.append(new TQTime(from)); + m_workingHours.append(new TQTime(until)); +} + +Calendar *Resource::calendar(bool local) const { + if (!local && project() != 0 && (m_calendar == 0 || m_calendar->isDeleted())) { + return project()->defaultCalendar(); + } + if (m_calendar && m_calendar->isDeleted()) { + return 0; + } + return m_calendar; +} + +DateTime Resource::getFirstAvailableTime(DateTime /*after*/) { + return DateTime(); +} + +DateTime Resource::getBestAvailableTime(Duration /*duration*/) { + return DateTime(); +} + +DateTime Resource::getBestAvailableTime(const DateTime /*after*/, const Duration /*duration*/) { + return DateTime(); +} + +bool Resource::load(TQDomElement &element) { + //kdDebug()<<k_funcinfo<<endl; + TQString s; + setId(element.attribute("id")); + m_name = element.attribute("name"); + m_initials = element.attribute("initials"); + m_email = element.attribute("email"); + setType(element.attribute("type")); + m_calendar = findCalendar(element.attribute("calendar-id")); + m_units = element.attribute("units", "100").toInt(); + s = element.attribute("available-from"); + if (s != "") + m_availableFrom = DateTime::fromString(s); + s = element.attribute("available-until"); + if (s != "") + m_availableUntil = DateTime::fromString(s); + + cost.normalRate = TDEGlobal::locale()->readMoney(element.attribute("normal-rate")); + cost.overtimeRate = TDEGlobal::locale()->readMoney(element.attribute("overtime-rate")); + return true; +} + +void Resource::save(TQDomElement &element) const { + //kdDebug()<<k_funcinfo<<endl; + TQDomElement 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("type", typeToString()); + me.setAttribute("units", m_units); + me.setAttribute("available-from", m_availableFrom.toString(Qt::ISODate)); + me.setAttribute("available-until", m_availableUntil.toString(Qt::ISODate)); + me.setAttribute("normal-rate", TDEGlobal::locale()->formatMoney(cost.normalRate)); + me.setAttribute("overtime-rate", TDEGlobal::locale()->formatMoney(cost.overtimeRate)); +} + +bool Resource::isAvailable(Task */*task*/) { + bool busy = false; +/* TQPtrListIterator<Appointment> it(m_appointments); + for (; it.current(); ++it) { + if (it.current()->isBusy(task->startTime(), task->endTime())) { + busy = true; + break; + } + }*/ + return !busy; +} + +TQPtrList<Appointment> Resource::appointments() { + TQPtrList<Appointment> lst; + if (m_currentSchedule) + lst = m_currentSchedule->appointments(); + //kdDebug()<<k_funcinfo<<lst.count()<<endl; + return lst; +} + +Appointment *Resource::findAppointment(Node */*node*/) { +/* TQPtrListIterator<Appointment> it = m_appointments; + for (; it.current(); ++it) { + if (it.current()->node() == node) + return it.current(); + }*/ + return 0; +} + +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); +} + +void Resource::addAppointment(Schedule *node, DateTime &start, DateTime &end, double load) { + //kdDebug()<<k_funcinfo<<endl; + Schedule *s = findSchedule(node->id()); + if (s == 0) { + s = createSchedule(node->parent()); + } + s->addAppointment(node, start, end, load); +} + +void Resource::initiateCalculation(Schedule &sch) { + m_currentSchedule = createSchedule(&sch); +} + +void Resource::removeSchedule(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.replace(schedule->id(), schedule); +} + +ResourceSchedule *Resource::createSchedule(TQString 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); + addSchedule(sch); + return sch; +} + +void Resource::makeAppointment(Schedule *node, const DateTime &from, const DateTime &end) { + if (!from.isValid() || !end.isValid()) { + kdWarning()<<k_funcinfo<<"Invalid time"<<endl; + return; + } + Calendar *cal = calendar(); + if (cal == 0) { + return; + } + DateTime time = from; + while (time < end) { + //kdDebug()<<k_funcinfo<<time.toString()<<" to "<<end.toString()<<endl; + if (!time.isValid() || !end.isValid()) { + kdWarning()<<k_funcinfo<<"Invalid time"<<endl; + return; + } + if (!cal->hasInterval(time, end)) { + //kdDebug()<<time.toString()<<" to "<<end.toString()<<": No (more) interval(s)"<<endl; + kdWarning()<<k_funcinfo<<m_name<<": Resource only partially available"<<endl; + //node->resourceNotAvailable = true; + return; // nothing more to do + } + TQPair<DateTime, DateTime> i = cal->firstInterval(time, end); + if (!i.second.isValid()) { + kdWarning()<<k_funcinfo<<"Invalid interval: "<<time<<", "<<end<<endl; + return; + } + if (time == i.second) + return; // hmmm, didn't get a new interval, avoid loop + addAppointment(node, i.first, i.second, m_units); + //kdDebug()<<k_funcinfo<<"Add :"<<i.first.toString()<<" to "<<i.second.toString()<<endl; + if (!(node->workStartTime.isValid()) || i.first < node->workStartTime) + node->workStartTime = i.first; + if (!(node->workEndTime.isValid()) || i.second > node->workEndTime) + node->workEndTime = i.second; + time = i.second; + } + return; +} +void Resource::makeAppointment(Schedule *node) { + //kdDebug()<<k_funcinfo<<m_name<< ": "<<node->node()->name()<<": "<<node->startTime.toString()<<" dur "<<node->duration.toString()<<endl; + if (!node->startTime.isValid()) { + kdWarning()<<k_funcinfo<<m_name<<": startTime invalid"<<endl; + return; + } + if (!node->endTime.isValid()) { + kdWarning()<<k_funcinfo<<m_name<<": endTime invalid"<<endl; + return; + } + 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); + } + if (!cal) { + kdWarning()<<k_funcinfo<<m_name<<": No calendar defined"<<endl; + return; + } + //TODO: units and standard non-working days + DateTime time = node->startTime; + DateTime end = node->endTime; + time = availableAfter(time, end); + if (!time.isValid()) { + kdWarning()<<k_funcinfo<<m_name<<": Resource not available (after="<<node->startTime<<", "<<end<<")"<<endl; + node->resourceNotAvailable = true; + return; + } + end = availableBefore(end, time); + if (!end.isValid()) { + kdWarning()<<k_funcinfo<<m_name<<": Resource not available (before="<<node->endTime<<", "<<time<<")"<<endl; + node->resourceNotAvailable = true; + return; + } + //kdDebug()<<k_funcinfo<<time.toString()<<" to "<<end.toString()<<endl; + makeAppointment(node, time, end); +} + +// the amount of effort we can do within the duration +Duration Resource::effort(const DateTime &start, const Duration &duration, bool backward, bool *ok) const { + //kdDebug()<<k_funcinfo<<m_name<<": "<<start.date().toString()<<" for duration "<<duration.toString(Duration::Format_Day)<<endl; + bool sts=false; + Duration e; + if (duration == 0) { + kdWarning()<<k_funcinfo<<"zero duration"<<endl; + return e; + } + Calendar *cal = calendar(); + if (cal == 0) { + kdWarning()<<k_funcinfo<<m_name<<": No calendar defined"<<endl; + return e; + } + if (backward) { + DateTime limit = start - duration; + DateTime t = availableBefore(start, limit); + if (t.isValid()) { + sts = true; + e = (cal->effort(limit, t) * m_units)/100; + } else { + //kdDebug()<<k_funcinfo<<m_name<<": Not available (start="<<start<<", "<<limit<<")"<<endl; + } + } else { + DateTime limit = start + duration; + DateTime t = availableAfter(start, limit); + if (t.isValid()) { + sts = true; + e = (cal->effort(t, limit) * m_units)/100; + } + } + //kdDebug()<<k_funcinfo<<start.toString()<<" e="<<e.toString(Duration::Format_Day)<<" ("<<m_units<<")"<<endl; + if (ok) *ok = sts; + return e; +} + +DateTime Resource::availableAfter(const DateTime &time, const DateTime limit, bool checkAppointments) const { + DateTime t; + if (m_units == 0) { + return t; + } + DateTime lmt = m_availableUntil; + if (limit.isValid() && limit < lmt) { + lmt = limit; + } + if (time >= lmt) { + return t; + } + if (type() == Type_Material) { + t = time > m_availableFrom ? time : m_availableFrom; + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<t.toString()<<" "<<m_name<<endl; + return t; + } + Calendar *cal = calendar(); + if (cal == 0) { + return t; + } + t = m_availableFrom > time ? m_availableFrom : time; + t = cal->firstAvailableAfter(t, lmt); + if (checkAppointments) { + //TODO + } + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<t.toString()<<" "<<m_name<<endl; + return t; +} + +DateTime Resource::availableBefore(const DateTime &time, const DateTime limit, bool checkAppointments) const { + DateTime t; + if (m_units == 0) { + return t; + } + DateTime lmt = m_availableFrom; + if (limit.isValid() && limit > lmt) { + lmt = limit; + } + if (time <= lmt) { + return t; + } + if (type() == Type_Material) { + t = time < m_availableUntil ? time : m_availableUntil; + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<t.toString()<<" "<<m_name<<endl; + return t; + } + Calendar *cal = calendar(); + if (cal == 0) { + return t; + } + if (!m_availableUntil.isValid()) { + kdWarning()<<k_funcinfo<<m_name<<": availabelUntil is invalid"<<endl; + t = time; + } else { + t = m_availableUntil < time ? m_availableUntil : time; + } + //kdDebug()<<k_funcinfo<<t<<", "<<lmt<<endl; + t = cal->firstAvailableBefore(t, lmt); + if (checkAppointments) { + //TODO + } + //kdDebug()<<k_funcinfo<<m_name<<" returns: "<<time<<"="<<t<<" "<<endl; + return t; +} + +Resource *Resource::findId(const TQString &id) const { + return m_project ? m_project->findResource(id) : 0; +} + +bool Resource::removeId(const TQString &id) { + return m_project ? m_project->removeResourceId(id) : false; +} + +void Resource::insertId(const TQString &id) { + if (m_project) + m_project->insertResourceId(id, this); +} + +Calendar *Resource::findCalendar(const TQString &id) const { + return (m_project ? m_project->findCalendar(id) : 0); +} + +bool Resource::isOverbooked() const { + return isOverbooked(DateTime(), DateTime()); +} + +bool Resource::isOverbooked(const TQDate &date) const { + return isOverbooked(DateTime(date), DateTime(date.addDays(1))); +} + +bool Resource::isOverbooked(const DateTime &start, const DateTime &end) const { + //kdDebug()<<k_funcinfo<<m_name<<": "<<start.toString()<<" - "<<end.toString()<<" cs=("<<m_currentSchedule<<")"<<endl; + return m_currentSchedule ? m_currentSchedule->isOverbooked(start, end) : false; +} + +Appointment Resource::appointmentIntervals() const { + Appointment a; + if (m_currentSchedule == 0) + return a; + TQPtrListIterator<Appointment> it = m_currentSchedule->appointments(); + for (; it.current(); ++it) { + a += *(it.current()); + } + return a; +} + +Duration Resource::plannedEffort(const TQDate &date) const { + return m_currentSchedule ? m_currentSchedule->plannedEffort(date) : Duration::zeroDuration; +} + +///////// Risk ///////// +Risk::Risk(Node *n, Resource *r, RiskType rt) { + m_node=n; + m_resource=r; + m_riskType=rt; +} + +Risk::~Risk() { +} + +ResourceRequest::ResourceRequest(Resource *resource, int units) + : m_resource(resource), + m_units(units), + m_parent(0) { + //kdDebug()<<k_funcinfo<<"("<<this<<") Request to: "<<(resource ? resource->name() : TQString("None"))<<endl; +} + +ResourceRequest::~ResourceRequest() { + //kdDebug()<<k_funcinfo<<"("<<this<<") resource: "<<(m_resource ? m_resource->name() : TQString("None"))<<endl; + if (m_resource) + m_resource->unregisterRequest(this); + m_resource = 0; +} + +bool ResourceRequest::load(TQDomElement &element, Project &project) { + //kdDebug()<<k_funcinfo<<endl; + m_resource = project.resource(element.attribute("resource-id")); + if (m_resource == 0) { + kdWarning()<<k_funcinfo<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id")<<endl; + return false; + } + m_units = element.attribute("units").toInt(); + return true; +} + +void ResourceRequest::save(TQDomElement &element) const { + TQDomElement me = element.ownerDocument().createElement("resource-request"); + element.appendChild(me); + me.setAttribute("resource-id", m_resource->id()); + me.setAttribute("units", m_units); +} + +int ResourceRequest::units() const { + //kdDebug()<<k_funcinfo<<m_resource->name()<<": units="<<m_units<<endl; + return m_units; +} + +int ResourceRequest::workUnits() const { + if (m_resource->type() == Resource::Type_Work) + return units(); + + //kdDebug()<<k_funcinfo<<"units=0"<<endl; + return 0; +} + +Task *ResourceRequest::task() const { + return m_parent ? m_parent->task() : 0; +} + +///////// +ResourceGroupRequest::ResourceGroupRequest(ResourceGroup *group, int units) + : m_group(group), m_units(units) { + + //kdDebug()<<k_funcinfo<<"Request to: "<<(group ? group->name() : TQString("None"))<<endl; + if (group) + group->registerRequest(this); + m_resourceRequests.setAutoDelete(true); +} + +ResourceGroupRequest::~ResourceGroupRequest() { + //kdDebug()<<k_funcinfo<<"Group: "<<m_group->name()<<endl; + if (m_group) + m_group->unregisterRequest(this); + m_resourceRequests.clear(); +} + +void ResourceGroupRequest::addResourceRequest(ResourceRequest *request) { + //kdDebug()<<k_funcinfo<<"("<<request<<") to Group: "<<m_group->name()<<endl; + request->setParent(this); + m_resourceRequests.append(request); + request->registerRequest(); +} + +ResourceRequest *ResourceGroupRequest::takeResourceRequest(ResourceRequest *request) { + if (request) + request->unregisterRequest(); + return m_resourceRequests.take(m_resourceRequests.findRef(request)); +} + +ResourceRequest *ResourceGroupRequest::find(Resource *resource) const { + TQPtrListIterator<ResourceRequest> it(m_resourceRequests); + for (; it.current(); ++it) + if (it.current()->resource() == resource) + return it.current(); + + return 0; +} + +bool ResourceGroupRequest::load(TQDomElement &element, Project &project) { + //kdDebug()<<k_funcinfo<<endl; + m_group = project.findResourceGroup(element.attribute("group-id")); + if (m_group == 0) { + //kdDebug()<<k_funcinfo<<"The referenced resource group does not exist: group id="<<element.attribute("group-id")<<endl; + return false; + } + m_group->registerRequest(this); + + m_units = element.attribute("units").toInt(); + + TQDomNodeList list = element.childNodes(); + for (unsigned int i=0; i<list.count(); ++i) { + if (list.item(i).isElement()) { + TQDomElement e = list.item(i).toElement(); + if (e.tagName() == "resource-request") { + ResourceRequest *r = new ResourceRequest(); + if (r->load(e, project)) + addResourceRequest(r); + else { + kdError()<<k_funcinfo<<"Failed to load resource request"<<endl; + delete r; + } + } + } + } + return true; +} + +void ResourceGroupRequest::save(TQDomElement &element) const { + if (units() == 0) + return; + TQDomElement me = element.ownerDocument().createElement("resourcegroup-request"); + element.appendChild(me); + me.setAttribute("group-id", m_group->id()); + me.setAttribute("units", m_units); + TQPtrListIterator<ResourceRequest> it(m_resourceRequests); + for (; it.current(); ++it) + it.current()->save(me); +} + +int ResourceGroupRequest::units() const { + int units = m_units; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + units += it.current()->units(); + } + //kdDebug()<<k_funcinfo<<"units="<<units<<endl; + return units; +} + +int ResourceGroupRequest::workUnits() const { + int units = 0; + if (m_group->type() == ResourceGroup::Type_Work) + units = m_units; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + units += it.current()->workUnits(); + } + //kdDebug()<<k_funcinfo<<"units="<<units<<endl; + return units; +} + +//TODO: handle nonspecific resources +Duration ResourceGroupRequest::effort(const DateTime &time, const Duration &duration, bool backward, bool *ok) const { + Duration e; + bool sts=false; + if (ok) *ok = sts; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + e += it.current()->resource()->effort(time, duration, backward, &sts); + if (sts && ok) *ok = sts; + //kdDebug()<<k_funcinfo<<(backward?"(B)":"(F)" )<<it.current()->resource()->name()<<": time="<<time<<" dur="<<duration.toString()<<"gave e="<<e.toString()<<endl; + } + //kdDebug()<<k_funcinfo<<time.toString()<<"d="<<duration.toString()<<": e="<<e.toString()<<endl; + return e; +} + +int ResourceGroupRequest::numDays(const DateTime &time, bool backward) const { + DateTime t1, t2 = time; + if (backward) { + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + t1 = it.current()->resource()->availableFrom(); + if (!t2.isValid() || t2 > t1) + t2 = t1; + } + //kdDebug()<<k_funcinfo<<"bw "<<time.toString()<<": "<<t2.daysTo(time)<<endl; + return t2.daysTo(time); + } + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + t1 = it.current()->resource()->availableUntil(); + if (!t2.isValid() || t2 < t1) + t2 = t1; + } + //kdDebug()<<k_funcinfo<<"fw "<<time.toString()<<": "<<time.daysTo(t2)<<endl; + return time.daysTo(t2); +} + +Duration ResourceGroupRequest::duration(const DateTime &time, const Duration &_effort, bool backward) { + //kdDebug()<<k_funcinfo<<"--->"<<(backward?"(B) ":"(F) ")<<m_group->name()<<" "<<time.toString()<<": effort: "<<_effort.toString(Duration::Format_Day)<<" ("<<_effort.milliseconds()<<")"<<endl; + Duration e; + if (_effort == Duration::zeroDuration) { + return e; + } + bool sts=true; + bool match = false; + DateTime start = time; + int inc = backward ? -1 : 1; + DateTime end = start; + Duration e1; + Duration d(1, 0, 0); // 1 day + int nDays = numDays(time, backward) + 1; + for (int i=0; !match && i <= nDays; ++i) { + // days + end = TQDateTime(end.addDays(inc)); + e1 = effort(start, d, backward, &sts); + //kdDebug()<<"["<<i<<"of"<<nDays<<"] "<<(backward?"(B)":"(F):")<<" start="<<start<<" e+e1="<<(e+e1).toString()<<" match "<<_effort.toString()<<endl; + if (e + e1 < _effort) { + e += e1; + start = end; + } else if (e + e1 == _effort) { + e += e1; + match = true; + } else { + end = start; + break; + } + } + //kdDebug()<<"duration "<<(backward?"backward ":"forward: ")<<start.toString()<<" - "<<end.toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<") match="<<match<<" sts="<<sts<<endl; + d = Duration(0, 1, 0); // 1 hour + for (int i=0; !match && i < 24; ++i) { + // hours + end = TQDateTime(end.addSecs(inc*60*60)); + e1 = effort(start, d, backward, &sts); + if (e + e1 < _effort) { + e += e1; + start = end; + } else if (e + e1 == _effort) { + e += e1; + match = true; + } else { + end = start; + break; + } + //kdDebug()<<"duration(h)["<<i<<"]"<<(backward?"backward ":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")"<<endl; + } + //kdDebug()<<"duration "<<(backward?"backward ":"forward: ")<<start.toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<") match="<<match<<" sts="<<sts<<endl; + d = Duration(0, 0, 1); // 1 minute + for (int i=0; !match && i < 60; ++i) { + //minutes + end = TQDateTime(end.addSecs(inc*60)); + e1 = effort(start, d, backward, &sts); + 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; + } + //kdDebug()<<"duration(m) "<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")"<<endl; + } + //kdDebug()<<"duration "<<(backward?"backward":"forward:")<<" start="<<start.toString()<<" e="<<e.toString()<<" match="<<match<<" sts="<<sts<<endl; + d = Duration(0, 0, 0, 1); // 1 second + for (int i=0; !match && i < 60 && sts; ++i) { + //seconds + end = TQDateTime(end.addSecs(inc)); + e1 = effort(start, d, backward, &sts); + 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; + } + //kdDebug()<<"duration(s)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")"<<endl; + } + d = Duration(0, 0, 0, 0, 1); // 1 millisecond + for (int i=0; !match && i < 1000; ++i) { + //milliseconds + end.setTime(end.time().addMSecs(inc)); + e1 = effort(start, d, backward, &sts); + if (e + e1 < _effort) { + e += e1; + start = end; + } else if (e + e1 == _effort) { + e += e1; + match = true; + } else if (e + e1 > _effort) { + break; + } + //kdDebug()<<"duration(ms)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")"<<endl; + } + if (!match) { + kdError()<<k_funcinfo<<(task()?task()->name():"No task")<<" "<<time<<": Could not match effort."<<" Want: "<<_effort.toString(Duration::Format_Day)<<" got: "<<e.toString(Duration::Format_Day)<<" sts="<<sts<<endl; + } + DateTime t; + if (e != Duration::zeroDuration) { + t = backward ? availableAfter(end) : availableBefore(end); + } + end = t.isValid() ? t : time; + //kdDebug()<<k_funcinfo<<"<---"<<(backward?"(B) ":"(F) ")<<m_group->name()<<": "<<end.toString()<<"-"<<time.toString()<<"="<<(end - time).toString()<<" effort: "<<_effort.toString(Duration::Format_Day)<<endl; + return (end>time?end-time:time-end); +} + +DateTime ResourceGroupRequest::availableAfter(const DateTime &time) { + DateTime start; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + DateTime t = it.current()->resource()->availableAfter(time); + if (t.isValid() && (!start.isValid() || t < start)) + start = t; + } + if (start.isValid() && start < time) + start = time; + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<start.toString()<<" "<<m_group->name()<<endl; + return start; +} + +DateTime ResourceGroupRequest::availableBefore(const DateTime &time) { + DateTime end; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + DateTime t = it.current()->resource()->availableBefore(time); + if (t.isValid() && (!end.isValid() || t > end)) + end = t; + } + if (!end.isValid() || end > time) + end = time; + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<end.toString()<<" "<<m_group->name()<<endl; + return end; +} + +void ResourceGroupRequest::makeAppointments(Schedule *schedule) { + //kdDebug()<<k_funcinfo<<endl; + TQPtrListIterator<ResourceRequest> it = m_resourceRequests; + for (; it.current(); ++it) { + it.current()->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; +} + +///////// +ResourceRequestCollection::ResourceRequestCollection(Task &task) + : m_task(task) { + m_requests.setAutoDelete(true); +} + +ResourceRequestCollection::~ResourceRequestCollection() { + //kdDebug()<<k_funcinfo<<"Group: "<<m_group->name()<<endl; + m_requests.clear(); +} + +ResourceGroupRequest *ResourceRequestCollection::find(ResourceGroup *group) const { + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + if (it.current()->group() == group) + return it.current(); // we assume only one request to the same group + } + return 0; +} + + +ResourceRequest *ResourceRequestCollection::find(Resource *resource) const { + ResourceRequest *req = 0; + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; !req && it.current(); ++it) { + req = it.current()->find(resource); + } + return req; +} + +// bool ResourceRequestCollection::load(TQDomElement &element, Project &project) { +// //kdDebug()<<k_funcinfo<<endl; +// return true; +// } + +void ResourceRequestCollection::save(TQDomElement &element) const { + //kdDebug()<<k_funcinfo<<endl; + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for ( ; it.current(); ++it ) { + it.current()->save(element); + } +} + +int ResourceRequestCollection::units() const { + //kdDebug()<<k_funcinfo<<endl; + int units = 0; + TQPtrListIterator<ResourceGroupRequest> it = m_requests; + for (; it.current(); ++it) { + units += it.current()->units(); + //kdDebug()<<k_funcinfo<<" Group: "<<it.current()->group()->name()<<" now="<<units<<endl; + } + return units; +} + +int ResourceRequestCollection::workUnits() const { + //kdDebug()<<k_funcinfo<<endl; + int units = 0; + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + units += it.current()->workUnits(); + } + //kdDebug()<<k_funcinfo<<" units="<<units<<endl; + return units; +} + +// Returns the longest duration needed by any of the groups. +// The effort is distributed on "work type" resourcegroups in proportion to +// the amount of resources requested for each group. +// "Material type" of resourcegroups does not (atm) affect the duration. +Duration ResourceRequestCollection::duration(const DateTime &time, const Duration &effort, bool backward) { + //kdDebug()<<k_funcinfo<<"time="<<time.toString()<<" effort="<<effort.toString(Duration::Format_Day)<<" backward="<<backward<<endl; + if (isEmpty()) { + return effort; + } + Duration dur; + int units = workUnits(); + if (units == 0) + units = 100; //hmmmm + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + if (it.current()->isEmpty()) + continue; + if (it.current()->group()->type() == ResourceGroup::Type_Work) { + Duration d = it.current()->duration(time, (effort*it.current()->workUnits())/units, backward); + if (d > dur) + dur = d; + } else if (it.current()->group()->type() == ResourceGroup::Type_Material) { + //TODO + if (dur == Duration::zeroDuration) + dur = effort; + } + } + return dur; +} + +DateTime ResourceRequestCollection::availableAfter(const DateTime &time) { + DateTime start; + TQPtrListIterator<ResourceGroupRequest> it = m_requests; + for (; it.current(); ++it) { + DateTime t = it.current()->availableAfter(time); + if (t.isValid() && (!start.isValid() || t < start)) + start = t; + } + if (start.isValid() && start < time) + start = time; + //kdDebug()<<k_funcinfo<<time.toString()<<"="<<start.toString()<<endl; + return start; +} + +DateTime ResourceRequestCollection::availableBefore(const DateTime &time) { + DateTime end; + TQPtrListIterator<ResourceGroupRequest> it = m_requests; + for (; it.current(); ++it) { + DateTime t = it.current()->availableBefore(time); + if (t.isValid() && (!end.isValid() ||t > end)) + end = t; + } + if (!end.isValid() || end > time) + end = time; + return end; +} + + +void ResourceRequestCollection::makeAppointments(Schedule *schedule) { + //kdDebug()<<k_funcinfo<<endl; + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + it.current()->makeAppointments(schedule); + } +} + +void ResourceRequestCollection::reserve(const DateTime &start, const Duration &duration) { + //kdDebug()<<k_funcinfo<<endl; + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + it.current()->reserve(start, duration); + } +} + +bool ResourceRequestCollection::isEmpty() const { + TQPtrListIterator<ResourceGroupRequest> it(m_requests); + for (; it.current(); ++it) { + if (!it.current()->isEmpty()) + return false; + } + return true; +} +#ifndef NDEBUG + +void ResourceGroup::printDebug(TQString indent) +{ + kdDebug()<<indent<<" + Resource group: "<<m_name<<" id="<<m_id<<endl; + indent += " !"; + TQPtrListIterator<Resource> it(m_resources); + for ( ; it.current(); ++it) + it.current()->printDebug(indent); +} +void Resource::printDebug(TQString indent) +{ + kdDebug()<<indent<<" + Resource: "<<m_name<<" id="<<m_id/*<<" Overbooked="<<isOverbooked()*/<<endl; + TQIntDictIterator<Schedule> it(m_schedules); + indent += " "; + for (; it.current(); ++it) { + it.current()->printDebug(indent); + } + indent += " !"; +} + +void ResourceGroupRequest::printDebug(TQString indent) +{ + kdDebug()<<indent<<" + Request to group: "<<(m_group ? m_group->name() : "None")<<" units="<<m_units<<"%"<<endl; + indent += " !"; + TQPtrListIterator<ResourceRequest> it(m_resourceRequests); + for (; it.current(); ++it) { + it.current()->printDebug(indent); + } +} + +void ResourceRequest::printDebug(TQString indent) +{ + kdDebug()<<indent<<" + Request to resource: "<<(m_resource ? m_resource->name() : "None")<<" units="<<m_units<<"%"<<endl; +} + +void ResourceRequestCollection::printDebug(TQString indent) +{ + kdDebug()<<indent<<" + Resource requests:"<<endl; + TQPtrListIterator<ResourceGroupRequest> it = m_requests; + for (; it.current(); ++it) { + it.current()->printDebug(indent+" "); + } +} +#endif + +} //KPlato namespace |